├── frontend
├── README.md
├── public
│ ├── _redirects
│ ├── robots.txt
│ ├── close.png
│ ├── edit.png
│ ├── logo.png
│ ├── menu.png
│ ├── user.png
│ ├── camera.png
│ ├── default.png
│ ├── delete.png
│ ├── favicon.ico
│ ├── logo16.png
│ ├── logo192.png
│ ├── logo24.png
│ ├── logo32.png
│ ├── logo512.png
│ ├── logo64.png
│ ├── add_button.png
│ ├── animales.png
│ ├── manifest.json
│ ├── index.html
│ ├── bootstrap-4.6.0-dist
│ │ └── css
│ │ │ ├── bootstrap-reboot.min.css
│ │ │ └── bootstrap-reboot.css
│ ├── firebase-messaging-sw.js
│ └── js
│ │ └── popper.min.js
├── .directory
├── .netlify
│ └── state.json
├── src
│ ├── Utils
│ │ └── Bus.js
│ ├── background.png
│ ├── TEXTURA 3-01.png
│ ├── TEXTURA 4-01.png
│ ├── TEXTURA 5-01.png
│ ├── TEXTURA 6-01.png
│ ├── Components
│ │ ├── Layout
│ │ │ ├── Logo
│ │ │ │ ├── Logo.css
│ │ │ │ └── Logo.js
│ │ │ ├── FilterForm
│ │ │ │ ├── FilterForm.css
│ │ │ │ └── FilterForm.js
│ │ │ ├── Card
│ │ │ │ ├── Card.css
│ │ │ │ └── Card.js
│ │ │ ├── Backdrop
│ │ │ │ ├── Backdrop.css
│ │ │ │ └── Backdrop.js
│ │ │ ├── Error
│ │ │ │ └── Error.js
│ │ │ ├── Spinner
│ │ │ │ ├── Spinner.js
│ │ │ │ └── Spinner.css
│ │ │ ├── Layout.css
│ │ │ ├── NavigationItems
│ │ │ │ ├── NavigationItems.css
│ │ │ │ ├── NavigationItem
│ │ │ │ │ ├── NavigationItem.js
│ │ │ │ │ └── NavigationItem.css
│ │ │ │ └── Navigationitems.js
│ │ │ ├── Toolbar
│ │ │ │ ├── Toolbar.js
│ │ │ │ └── Toolbar.css
│ │ │ ├── Modal
│ │ │ │ ├── Modal.css
│ │ │ │ └── Modal.js
│ │ │ ├── SideDrawer
│ │ │ │ ├── SideDrawer.css
│ │ │ │ └── SideDrawer.js
│ │ │ ├── Layout.js
│ │ │ ├── PictureModal
│ │ │ │ └── PictureModal.js
│ │ │ ├── Input
│ │ │ │ └── Input.js
│ │ │ ├── UserIcon
│ │ │ │ └── UserIcon.js
│ │ │ ├── PostsTabs
│ │ │ │ └── PostsTabs.js
│ │ │ └── MenuIcon
│ │ │ │ └── MenuIcon.js
│ │ ├── AboutUs
│ │ │ ├── AboutUs.css
│ │ │ └── AboutUs.js
│ │ ├── TopHeader
│ │ │ ├── TopHeader.css
│ │ │ └── TopHeader.js
│ │ ├── Footer
│ │ │ ├── Footer.css
│ │ │ └── Footer.js
│ │ ├── Flash
│ │ │ ├── Flash.css
│ │ │ └── Flash.js
│ │ ├── PostDetails
│ │ │ ├── PostDetails.css
│ │ │ └── PostDetails.js
│ │ ├── Posts
│ │ │ ├── Posts.css
│ │ │ ├── Post
│ │ │ │ ├── Post.css
│ │ │ │ └── Post.js
│ │ │ └── Posts.js
│ │ ├── NewPost
│ │ │ ├── NewPost.css
│ │ │ └── NewPost.js
│ │ ├── MyPosts
│ │ │ └── MyPosts.js
│ │ ├── Authentication
│ │ │ ├── Login.js
│ │ │ └── Register.js
│ │ └── EditPost
│ │ │ └── EditPost.js
│ ├── setupTests.js
│ ├── App.test.js
│ ├── store
│ │ ├── actions.js
│ │ └── reducer.js
│ ├── reportWebVitals.js
│ ├── index.css
│ ├── App.css
│ ├── Constants
│ │ └── constants.js
│ ├── App.js
│ ├── index.js
│ └── logo.svg
└── package.json
├── backend
├── static
│ └── pictures
│ │ └── .gitinclude
├── controllers
│ └── controller.js
├── jsconfig.json
├── models
│ ├── token.js
│ ├── user.js
│ └── alert.js
├── managers
│ ├── user-manager.js
│ ├── token-manager.js
│ ├── manager.js
│ └── alert-manager.js
├── test
│ ├── data
│ │ └── data.js
│ ├── cleaner.js
│ ├── manual-test.js
│ ├── data-access
│ │ └── user.js
│ └── endpoints
│ │ ├── token.js
│ │ ├── user.js
│ │ └── alert.js
├── config
│ ├── constant.js
│ └── mongoose.js
├── README.md
├── package.json
├── repositories
│ └── repository.js
└── index.js
├── push-notification-test
├── private-key.txt
├── key-pair.txt
├── server-key.txt
├── package.json
├── firebase.js
├── request-notification-template.txt
├── firebase-messaging-sw.js
├── firebase-script.html
└── yarn.lock
├── .directory
├── .github
└── workflows
│ └── backend-test.yml
├── LICENSE
├── .gitignore
├── README.md
└── CONTRIBUTING.md
/frontend/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/static/pictures/.gitinclude:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/backend/controllers/controller.js:
--------------------------------------------------------------------------------
1 | import { get } from "mongoose";
2 |
--------------------------------------------------------------------------------
/push-notification-test/private-key.txt:
--------------------------------------------------------------------------------
1 | OSwKRaPU17JdUQwgol8iTORvPyd-PKskVX_A5FT2bZ8
--------------------------------------------------------------------------------
/.directory:
--------------------------------------------------------------------------------
1 | [Dolphin]
2 | Timestamp=2021,5,19,16,4,4
3 | Version=4
4 | ViewMode=1
5 |
--------------------------------------------------------------------------------
/frontend/.directory:
--------------------------------------------------------------------------------
1 | [Dolphin]
2 | Timestamp=2021,5,13,17,0,1
3 | Version=4
4 | ViewMode=1
5 |
--------------------------------------------------------------------------------
/frontend/.netlify/state.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteId": "f0f3f40c-3b55-4d72-af81-79b279347fbc"
3 | }
--------------------------------------------------------------------------------
/frontend/src/Utils/Bus.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from "events"
2 |
3 | export default new EventEmitter()
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/public/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/close.png
--------------------------------------------------------------------------------
/frontend/public/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/edit.png
--------------------------------------------------------------------------------
/frontend/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo.png
--------------------------------------------------------------------------------
/frontend/public/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/menu.png
--------------------------------------------------------------------------------
/frontend/public/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/user.png
--------------------------------------------------------------------------------
/frontend/public/camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/camera.png
--------------------------------------------------------------------------------
/frontend/public/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/default.png
--------------------------------------------------------------------------------
/frontend/public/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/delete.png
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/logo16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo16.png
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo24.png
--------------------------------------------------------------------------------
/frontend/public/logo32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo32.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/logo64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/logo64.png
--------------------------------------------------------------------------------
/frontend/src/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/src/background.png
--------------------------------------------------------------------------------
/frontend/public/add_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/add_button.png
--------------------------------------------------------------------------------
/frontend/public/animales.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/public/animales.png
--------------------------------------------------------------------------------
/frontend/src/TEXTURA 3-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/src/TEXTURA 3-01.png
--------------------------------------------------------------------------------
/frontend/src/TEXTURA 4-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/src/TEXTURA 4-01.png
--------------------------------------------------------------------------------
/frontend/src/TEXTURA 5-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/src/TEXTURA 5-01.png
--------------------------------------------------------------------------------
/frontend/src/TEXTURA 6-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tribilin-cuba/to-the-rescue/HEAD/frontend/src/TEXTURA 6-01.png
--------------------------------------------------------------------------------
/push-notification-test/key-pair.txt:
--------------------------------------------------------------------------------
1 | BAq83TbJIFswfdiiTny27GwlEr6SJF-CMUvF2iuK8V7_wOqMXpT8k5TgtQkXLFWhwjMDf81Ez9ks1kGANUdJ0AU
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Logo/Logo.css:
--------------------------------------------------------------------------------
1 | .Logo {
2 | background-color: white;
3 | padding: 8px;
4 | height: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/FilterForm/FilterForm.css:
--------------------------------------------------------------------------------
1 | .filter-header {
2 | font-weight: bold;
3 | color: #edc00f;
4 | border-bottom: 1px solid lightgray;
5 | }
6 |
--------------------------------------------------------------------------------
/push-notification-test/server-key.txt:
--------------------------------------------------------------------------------
1 | AAAAFK1fL-w:APA91bHZgYm-eIXt95rBvIuoWCOGX3W6DqudtmskiW2KDnVHOZMExApFBDf7N-U2mibj76zNFjISy1FHLtCdW91cAnghozk0UBYZnBvV7HUwVTHg_EJMP2pEuR1V_MujVuTzpqx5RIof
--------------------------------------------------------------------------------
/frontend/src/Components/AboutUs/AboutUs.css:
--------------------------------------------------------------------------------
1 | .about-us-div {
2 | display: flex;
3 | justify-content: flex-start;
4 | }
5 | .about-us-div h5 {
6 | color: #edc00f;
7 | font-weight: bold;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Card/Card.css:
--------------------------------------------------------------------------------
1 | .Card {
2 | width: 100%;
3 | height: 90px;
4 | margin-top: 15px;
5 | border-radius: 10px 10px 0px 10px;
6 | background-color: white;
7 | border: lightgray solid 1px;
8 | }
9 |
--------------------------------------------------------------------------------
/push-notification-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "push-notification-test",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "firebase": "^8.4.2"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Backdrop/Backdrop.css:
--------------------------------------------------------------------------------
1 | .Backdrop {
2 | width: 100%;
3 | height: 100%;
4 | position: fixed;
5 | z-index: 100;
6 | left: 0;
7 | top: 0;
8 | background-color: rgba(0, 0, 0, 0.5);
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Backdrop/Backdrop.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import "./Backdrop.css"
3 | const backdrop = (props) => (
4 | props.show ?
: null
5 | )
6 |
7 | export default backdrop
--------------------------------------------------------------------------------
/backend/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "./**/*"
4 | ],
5 | "compilerOptions": {
6 | "target": "esnext",
7 | "module": "es2015",
8 | "allowSyntheticDefaultImports": true,
9 | "experimentalDecorators": true
10 | }
11 | }
--------------------------------------------------------------------------------
/frontend/src/setupTests.js:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Error/Error.js:
--------------------------------------------------------------------------------
1 | import { Alert } from "react-bootstrap";
2 |
3 | function Error({ message }) {
4 | return (
5 |
6 | Ups algo salio mal: {message}
7 |
8 | )
9 | }
10 | export default Error
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Card/Card.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import "./Card.css"
3 |
4 | const card = (props) => (
5 |
6 |
7 | {props.children}
8 |
9 |
10 | )
11 |
12 | export default card
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Spinner/Spinner.js:
--------------------------------------------------------------------------------
1 | import "./Spinner.css"
2 | function Spinner() {
3 | return (
4 |
7 | )
8 | }
9 |
10 | export default Spinner
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Layout.css:
--------------------------------------------------------------------------------
1 | .Content {
2 | margin-top: 65px;
3 | margin-left: 15px;
4 | margin-right: 15px;
5 | margin-bottom: 15px;
6 | font-family: Verdana, Geneva, Tahoma, sans-serif;
7 | color: #464646;
8 | overflow-x: hidden;
9 | overflow: hidden;
10 | max-width: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Logo/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import "./Logo.css"
3 |
4 | const logo = (props) => (
5 |
6 |
7 |
8 | )
9 |
10 | export default logo;
--------------------------------------------------------------------------------
/frontend/src/Components/TopHeader/TopHeader.css:
--------------------------------------------------------------------------------
1 | .TopHeader {
2 | width: 100%;
3 | }
4 |
5 | .TopHeaderDiv {
6 | width: 100%;
7 | }
8 |
9 | .TopHeaderText {
10 | color: #e27e22;
11 | margin-bottom: 7px !important;
12 | font-weight: bold;
13 | }
14 |
15 | .TopHeaderIcon {
16 | margin-left: 5px;
17 | }
18 |
--------------------------------------------------------------------------------
/backend/models/token.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | var TokenSchema = new mongoose.Schema({
4 | token:{
5 | type: String,
6 | required: true
7 | },
8 | user_id: {
9 | type: String
10 | }
11 | })
12 |
13 | var TokenModel = mongoose.model('token', TokenSchema)
14 |
15 | export default TokenModel
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/NavigationItems/NavigationItems.css:
--------------------------------------------------------------------------------
1 | .NavigationItems {
2 | margin: 0;
3 | padding: 0;
4 | list-style: none;
5 | display: flex;
6 | align-items: center;
7 | flex-flow: column;
8 | height: 100%;
9 | }
10 |
11 | @media (min-width: 500px) {
12 | .NavigationItems {
13 | flex-flow: row;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/backend/managers/user-manager.js:
--------------------------------------------------------------------------------
1 | import UserModel from "../models/user.js";
2 | import Repository from "../repositories/repository.js";
3 | import Manager from "./manager.js";
4 |
5 | class UserManager extends Manager{
6 |
7 | constructor(){
8 | super(new Repository(UserModel))
9 | }
10 |
11 | }
12 |
13 | export default UserManager
--------------------------------------------------------------------------------
/backend/test/data/data.js:
--------------------------------------------------------------------------------
1 | export const dummyUser = {
2 | firstName: 'John',
3 | lastName: 'Doe',
4 | email: 'john@doe.com'
5 | }
6 |
7 | export const dummyAlert = {
8 | author_id: '1',
9 | province: 'La Habana',
10 | municipality: 'Playa',
11 | alert_type: 'Perdido',
12 | description: 'Perrito en la esquina de 31 y 10'
13 | }
--------------------------------------------------------------------------------
/backend/managers/token-manager.js:
--------------------------------------------------------------------------------
1 | import TokenModel from "../models/token.js";
2 | import Repository from "../repositories/repository.js";
3 | import Manager from "./manager.js";
4 |
5 | class TokenManager extends Manager{
6 |
7 | constructor(){
8 | super(new Repository(TokenModel))
9 | }
10 |
11 | }
12 |
13 | export default TokenManager
--------------------------------------------------------------------------------
/frontend/src/Components/Footer/Footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | position: fixed;
3 | bottom: 0;
4 | left: 0;
5 | width: 100%;
6 | background-color: rgb(255, 250, 238);
7 | padding: 5px;
8 | }
9 |
10 | .footer a {
11 | color: #e27e22;
12 | font-weight: bold;
13 | padding: 0.5rem;
14 | }
15 |
16 | .footer a:hover {
17 | color: rgb(44, 43, 43);
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/NavigationItems/NavigationItem/NavigationItem.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import "./NavigationItem.css"
4 |
5 | const navigationItem = (props) => (
6 |
7 | {props.children}
8 |
9 | )
10 |
11 | export default navigationItem
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/NavigationItems/Navigationitems.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import "./NavigationItems.css"
3 | import NavigationItem from "./NavigationItem/NavigationItem"
4 |
5 | const navigationItems = (props) => (
6 |
9 | )
10 |
11 | export default navigationItems
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Toolbar/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import "./Toolbar.css"
3 | // import Logo from "../Logo/Logo"
4 | // import NavigationItems from "../NavigationItems/Navigationitems"
5 |
6 | const toolbar = (props) => (
7 |
8 |
9 |
10 | )
11 |
12 | export default toolbar
--------------------------------------------------------------------------------
/frontend/src/store/actions.js:
--------------------------------------------------------------------------------
1 |
2 | export const POPULATE_POSTS = "POPULATE_POSTS"
3 |
4 | export const POPULATE_SELECTED_POST = "POPULATE_SELECTED_POST"
5 |
6 | export const UPDATE_NEW_POST_INPUT = "UPDATE_NEW_POST_INPUT"
7 |
8 | export const OPEN_SIDE_DRAWER = "OPEN_SIDE_DRAWER"
9 |
10 | export const CLOSE_SIDE_DRAWER = "CLOSE_SIDE_DRAWER"
11 |
12 | export const SET_USER_ID = "SET_USER_ID"
13 |
14 | export const LOG_OUT = "LOG_OUT"
--------------------------------------------------------------------------------
/backend/config/constant.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv'
2 |
3 | dotenv.config()
4 | console.log(process.env.NODE_ENV);
5 |
6 | export const PICTURES_DIR = "/static/pictures"
7 |
8 | export const URL = process.env.URL && process.env.PORT ?
9 | process.env.URL + ':' + process.env.PORT :
10 | 'http://localhost:8080'
11 |
12 | export const TOY_DETA_KEY = 'b05adlw0_WSQqvBzkzp6x7XbJ6nQKA3Tx3C7nQQ6k'
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/backend/test/cleaner.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 | import MongooseConnection from "../config/mongoose.js"
3 |
4 | before((done) => {
5 | var connector = new MongooseConnection()
6 | connector.getConnection()
7 |
8 | while(connector.connection == null)
9 | continue
10 |
11 | connector.connection.dropDatabase()
12 |
13 | done()
14 | })
15 |
16 | after((done) => {
17 | mongoose.disconnect(done)
18 | })
--------------------------------------------------------------------------------
/frontend/src/Components/Flash/Flash.css:
--------------------------------------------------------------------------------
1 | .alert {
2 | top: 50px;
3 | right: 10px;
4 | display: flex;
5 | justify-content: center;
6 | }
7 | .alert-success {
8 | background-color: #2ec871;
9 | color: white;
10 | font-weight: bold;
11 | }
12 | .alert-error {
13 | background-color: #e34c3c;
14 | color: white;
15 | font-weight: bold;
16 | }
17 | .alert-info {
18 | background-color: #e27e22;
19 | color: white;
20 | font-weight: bold;
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Modal/Modal.css:
--------------------------------------------------------------------------------
1 | .Modal {
2 | position: fixed;
3 | z-index: 500;
4 | background-color: white;
5 | width: 100%;
6 | border: 1px solid #ccc;
7 | box-shadow: 1 1 1 black;
8 | padding: 16px;
9 | left: 15%;
10 | top: 30%;
11 | box-sizing: border-box;
12 | transition: all 0.3s ease-in-out;
13 | }
14 |
15 | @media (min-width: 600px) {
16 | .Modal {
17 | width: 500px;
18 | left: calc(50% - 250px);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/SideDrawer/SideDrawer.css:
--------------------------------------------------------------------------------
1 | .SideDrawer {
2 | position: fixed;
3 | width: 280px;
4 | max-width: 70%;
5 | height: 100%;
6 | left: 0;
7 | top: 0;
8 | z-index: 200;
9 | background-color: white;
10 | padding: 32px 16px;
11 | box-sizing: border-box;
12 | transition: transform 0.3s ease-out;
13 | }
14 |
15 | .Open {
16 | transform: translateX(45%);
17 | }
18 |
19 | .Close {
20 | transform: translateX(+500%);
21 | }
22 |
23 | .Logo {
24 | height: 11%;
25 | margin-bottom: 32px;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import "./Layout.css"
3 | import Toolbar from "./Toolbar/Toolbar"
4 | import { Component } from "react";
5 |
6 | class Layout extends Component {
7 |
8 | render() {
9 | return (
10 |
11 |
12 |
13 | {this.props.children}
14 |
15 |
16 | )
17 | }
18 |
19 | }
20 |
21 | export default Layout
--------------------------------------------------------------------------------
/backend/managers/manager.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | class Manager {
4 |
5 | constructor(repository){
6 | this.repository = repository
7 | }
8 |
9 |
10 | insert(entry){
11 | return this.repository.create(entry)
12 | }
13 |
14 | find(filters){
15 | return this.repository.find(filters)
16 | }
17 |
18 | update(filters, newFields){
19 | return this.repository.update(filters, newFields)
20 | }
21 |
22 | delete(filters){
23 | return this.repository.remove(filters)
24 | }
25 | }
26 |
27 | export default Manager
--------------------------------------------------------------------------------
/push-notification-test/firebase.js:
--------------------------------------------------------------------------------
1 | import firebase from "firebase";
2 | import "firebase/analytics";
3 | import "firebase/messaging";
4 |
5 | var firebaseConfig = {
6 | apiKey: "AIzaSyCOl76atKnpcjHsrK6MknUXRBekDK_dJl8",
7 | authDomain: "to-the-rescue-4c250.firebaseapp.com",
8 | projectId: "to-the-rescue-4c250",
9 | storageBucket: "to-the-rescue-4c250.appspot.com",
10 | messagingSenderId: "88808042476",
11 | appId: "1:88808042476:web:b3d36e2fff4b43c5c05ac9",
12 | measurementId: "G-SE04L5SX65"
13 | };
14 |
15 | firebase.initializeApp(firebaseConfig);
--------------------------------------------------------------------------------
/backend/test/manual-test.js:
--------------------------------------------------------------------------------
1 | // import chai from 'chai'
2 | // import chaiHttp from 'chai-http'
3 |
4 | // import app from "../app.js"
5 | // import { dummyUser, dummyAlert } from "./data/data.js";
6 |
7 | // const should = chai.should();
8 |
9 |
10 | // chai.use(chaiHttp)
11 |
12 | // chai.request(app)
13 | // .get('/user/' + "60f33f011c988f549c8928e2")
14 | // .end((err, res) => {
15 | // res.should.have.status(200)
16 | // res.body.should.be.a('object')
17 | // res.body.firstName.should.be.equal(dummyUser.firstName)
18 | // done()
19 | // })
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # "Al Rescate" API
2 |
3 | This is the API of "Al Rescate", a web application to help the animals that need us
4 |
5 | ## Setup
6 |
7 | You need to install MongoDb locally
8 |
9 | Then run ```yarn``` inside this directory and all the dependencies will be set up
10 | appropriately.
11 |
12 | ## Running the API locally
13 |
14 | Run ```yarn start``` and the API will be running in **http://localhost:8080**.
15 |
16 | To set another port change the PORT variable in the _.env_ file to the desired value.
17 |
18 | ## Running tests
19 |
20 | Run ```yarn test``` to run the tests
21 |
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Tribilin",
3 | "name": "Tribilin",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react"
2 | import "./Modal.css"
3 | import Backdrop from "../Backdrop/Backdrop"
4 |
5 |
6 | const modal = (props) => (
7 |
8 |
9 |
15 | {props.children}
16 |
17 |
18 |
19 | )
20 |
21 | export default modal
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/PictureModal/PictureModal.js:
--------------------------------------------------------------------------------
1 | import { Modal } from "react-bootstrap";
2 |
3 | function PictureModal({ show, imgSrc, handleClose }) {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default PictureModal
--------------------------------------------------------------------------------
/.github/workflows/backend-test.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push, pull_request]
4 |
5 | defaults:
6 | run:
7 | working-directory: backend
8 |
9 | jobs:
10 | build-node:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v1
14 | - name: Use Node.js 14.x
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 14.x
18 | - name: Start Docker for Mongodb
19 | run: docker run -d -p 27017:27017 mongo
20 | - name: yarn install and test
21 | run: |
22 | sudo yarn
23 | sudo yarn test
24 | env:
25 | CI: true
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Toolbar/Toolbar.css:
--------------------------------------------------------------------------------
1 | .Toolbar {
2 | height: 7%;
3 | width: 100%;
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | background-color: white;
8 | display: flex;
9 | justify-content: center;
10 | padding: 0 20px;
11 | box-sizing: border-box;
12 | z-index: 90;
13 | /* border-bottom: solid 3px #cccccc; */
14 | box-shadow: 0 4px 2px -2px rgba(0, 0, 0, 0.2);
15 | }
16 | .Toolbar img {
17 | height: 40px;
18 | }
19 |
20 | .Toolbar nav {
21 | height: 100%;
22 | }
23 |
24 | .Logo {
25 | height: 80%;
26 | }
27 |
28 | @media (max-width: 499px) {
29 | .DesktopOnly {
30 | display: none;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/Components/PostDetails/PostDetails.css:
--------------------------------------------------------------------------------
1 | .posts-details-edit-button {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | background-color: #edc00f;
6 | border-radius: 50% 50% 50% 50%;
7 | height: 60px;
8 | width: 60px;
9 | margin-bottom: 5px;
10 | }
11 | .posts-details-delete-button {
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | background-color: #e34c3c;
16 | border-radius: 50% 50% 50% 50%;
17 | height: 60px;
18 | width: 60px;
19 | }
20 |
21 | .posts-details-actions-div {
22 | position: fixed;
23 | bottom: 15px;
24 | right: 20px;
25 | z-index: 10;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(255, 250, 238);
3 | background-image: linear-gradient(
4 | rgba(255, 255, 255, 0.5),
5 | rgba(255, 255, 255, 0.5)
6 | );
7 | background-attachment: fixed;
8 | background-size: cover;
9 | margin: 0;
10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
11 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
12 | sans-serif;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | code {
18 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
19 | monospace;
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/Components/Posts/Posts.css:
--------------------------------------------------------------------------------
1 | .PostsAddButton {
2 | width: 60px;
3 | position: fixed;
4 | bottom: 60px;
5 | right: 20px;
6 | z-index: 10;
7 | }
8 | .posts-filter-button {
9 | background-color: #e27e22 !important;
10 | border-color: #e27e22 !important;
11 | font-weight: bold;
12 | margin-bottom: 10px;
13 | height: 30px;
14 | }
15 | .posts-filter-button:focus {
16 | box-shadow: rgba(226, 126, 34, 0.5) 0px 0px 0px 3.2px !important;
17 | }
18 | .posts-filter-button:hover {
19 | background-color: #e27e22;
20 | border-color: #e27e22;
21 | }
22 | .post-tabs {
23 | color: #e27e22;
24 | }
25 |
26 | .no-alerts {
27 | font-style: italic;
28 | color: #464646;
29 | margin-top: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/SideDrawer/SideDrawer.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import "./SideDrawer.css"
3 | import Backdrop from "../Backdrop/Backdrop"
4 | import Logo from "../Logo/Logo"
5 | // import NavigationItems from "../NavigationItems/Navigationitems"
6 |
7 | const SideDrawer = (props) => {
8 |
9 | let attachedClasses = ["SideDrawer", "Close"]
10 | if (props.open) {
11 | attachedClasses = ["SideDrawer", "Open"]
12 | }
13 | return (
14 |
22 | )
23 | }
24 | export default SideDrawer
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 |
36 | to {
37 | transform: rotate(360deg);
38 | }
39 | }
40 |
41 | .section {
42 | min-height: calc(100vh - 18rem);
43 | }
--------------------------------------------------------------------------------
/backend/models/user.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | var UserSchema = new mongoose.Schema({
4 | firstName: {
5 | type: String,
6 | maxLength: 30,
7 | required: false,
8 | },
9 |
10 | lastName: {
11 | type: String,
12 | maxLength: 30,
13 | required: false,
14 | },
15 |
16 | secondLastName: {
17 | type: String,
18 | maxLength: 30,
19 | required: false,
20 | },
21 |
22 | email: {
23 | type: String,
24 | maxLength: 30,
25 | required: true,
26 | },
27 |
28 | phone: {
29 | type: String,
30 | maxLength: 15,
31 | required: false,
32 | }
33 | })
34 |
35 | var UserModel = mongoose.model('user', UserSchema)
36 |
37 | export default UserModel
--------------------------------------------------------------------------------
/frontend/src/Constants/constants.js:
--------------------------------------------------------------------------------
1 | export const SERVER_URL = process.env.NODE_ENV === "development" ? "http://localhost:8080/" : "https://to-the-rescue-api-staging.herokuapp.com/"
2 |
3 | export const TOY_DETA_KEY = 'b05adlw0_WSQqvBzkzp6x7XbJ6nQKA3Tx3C7nQQ6k'
4 |
5 | export const TOY_DETA_ID = 'b05adlw0'
6 |
7 | export const DETA_URL = 'https://drive.deta.sh/v1/'
8 |
9 | export const DETA_PROJECT_ID = process.env.DETA_PROJECT_ID
10 |
11 | export const DETA_API_KEY = process.env.DETA_API_KEY
12 |
13 | export const PROVINCES = [
14 | "Pinar del río",
15 | "Artemisa",
16 | "La Habana",
17 | "Mayabeque",
18 | "Matanzas",
19 | "Cienfuegos",
20 | "Villa Clara",
21 | "Sancti Spíritus",
22 | "Ciego de Ávila",
23 | "Camagüey",
24 | "Las Tunas",
25 | "Granma",
26 | "Holguín",
27 | "Santiago de Cuba",
28 | "Guantánamo",
29 | "Isla de la Juventud"
30 | ]
--------------------------------------------------------------------------------
/push-notification-test/request-notification-template.txt:
--------------------------------------------------------------------------------
1 | https://fcm.googleapis.com//v1/projects//messages:send
2 | Content-Type: application/json
3 | Authorization: bearer AAAAFK1fL-w:APA91bHZgYm-eIXt95rBvIuoWCOGX3W6DqudtmskiW2KDnVHOZMExApFBDf7N-U2mibj76zNFjISy1FHLtCdW91cAnghozk0UBYZnBvV7HUwVTHg_EJMP2pEuR1V_MujVuTzpqx5RIof
4 |
5 | {
6 | "message": {
7 | "token": ,
8 | "notification": {
9 | "title": "Background Message Title",
10 | "body": "Background message body"
11 | },
12 | "webpush": {
13 | "fcm_options": {
14 | "link": "https://dummypage.com"
15 | }
16 | }
17 | }
18 | }
19 |
20 |
21 |
22 |
23 |
24 | curl -X POST -H "Authorization: key=YOUR_SERVER_KEY" -H "Content-Type: application/json" -d '{
25 | "notification": {
26 | "title": "First notif",
27 | "body": "Hello world",
28 | "icon": "firebase-logo.png",
29 | "click_action": "http://localhost:8081"
30 | },
31 | "to": "CLIENT_TOKEN"
32 | }' "https://fcm.googleapis.com/fcm/send"
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/NavigationItems/NavigationItem/NavigationItem.css:
--------------------------------------------------------------------------------
1 | /* .NavigationItem {
2 | margin: 10px 0;
3 | box-sizing: border-box;
4 | display: block;
5 | width: 100%;
6 | align-items: center;
7 | }
8 |
9 | .NavigationItem a {
10 | color: darkcyan;
11 | text-decoration: none;
12 | width: 100%;
13 | box-sizing: border-box;
14 | display: block;
15 | }
16 |
17 | .NavigationItem a:hover,
18 | .NavigationItem a:active,
19 | .NavigationItem a.active {
20 | border-bottom: 4px solid #40a4c8;
21 | }
22 |
23 | @media (min-width: 500px) { */
24 | .NavigationItem {
25 | margin: 0;
26 | display: flex;
27 | height: 100%;
28 | width: auto;
29 | align-items: center;
30 | }
31 |
32 | .NavigationItem a {
33 | color: white;
34 | height: 100%;
35 | padding: 16px 10px;
36 | border-bottom: 4px solid transparent;
37 | }
38 |
39 | .NavigationItem a:hover,
40 | .NavigationItem a:active,
41 | .NavigationItem a.active {
42 | background-color: darkcyan;
43 | border-bottom: 4px solid #40a4c8;
44 | color: white;
45 | }
46 | /* } */
47 |
--------------------------------------------------------------------------------
/frontend/src/Components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import { GrGithub } from "react-icons/gr";
2 | import { SiFacebook } from "react-icons/si";
3 | import { AiFillTwitterCircle, AiFillInstagram } from "react-icons/ai";
4 | import "./Footer.css";
5 |
6 | export default function Footer() {
7 | return (
8 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 José Jorge Rodríguez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/backend/config/mongoose.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 | import dotenv from 'dotenv'
3 |
4 | dotenv.config()
5 | class MongooseConnection{
6 |
7 | connection = null
8 |
9 | getConnection(){
10 |
11 | if(this.connection !== null)
12 | return this.connection
13 |
14 | mongoose.Promise = global.Promise
15 |
16 | const DB_URL = process.env.MONGODB_URL || "mongodb://localhost:27017/to-the-rescue-dev"
17 | console.log(DB_URL)
18 |
19 | mongoose.connect(DB_URL, {
20 | useNewUrlParser: true,
21 | useUnifiedTopology: true
22 | })
23 |
24 | mongoose.connection
25 | .once('open', () => {
26 | console.log('CONNECTED TO DB');
27 | })
28 | .on('error', (error)=> {
29 | console.error('CONNECTION ERROR:', error)
30 | process.exit(1)
31 | })
32 | .on('close', () => console.log('CLOSING CONNECTION'))
33 |
34 | this.connection = mongoose.connection
35 | }
36 | }
37 |
38 | export default MongooseConnection
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Input/Input.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const input = (props) => {
4 | let inputElement = null
5 | const style = {
6 | outline: "none",
7 | border: "1px solid #ccc",
8 | backgroundColor: "white",
9 | font: "inherit",
10 | padding: "6px 10px",
11 | display: "block",
12 | width: "100%"
13 | }
14 |
15 | switch (props.config.type) {
16 | case ('input'):
17 | inputElement = ;
18 | break;
19 | case ('textarea'):
20 | inputElement = ;
21 | break;
22 | default:
23 | inputElement =
24 | }
25 | return (
26 |
27 | {props.label}
28 | {inputElement}
29 |
30 | )
31 | }
32 |
33 |
34 |
35 |
36 | export default input
--------------------------------------------------------------------------------
/backend/models/alert.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 |
3 | var AlertSchema = new mongoose.Schema({
4 |
5 | author_id : {
6 | type: String,
7 | required: true
8 | },
9 |
10 | animal : {
11 | type: String,
12 | },
13 |
14 | gender : {
15 | type: String,
16 | enum:['Macho', 'Hembra', 'Desconocido']
17 | },
18 |
19 | age: {
20 | type: String
21 | },
22 |
23 | date: {
24 | type: Date,
25 | default: Date.now()
26 | },
27 |
28 | picture_path: {
29 | type: String,
30 | },
31 |
32 | province: {
33 | type: String,
34 | required: true
35 | },
36 |
37 | municipality: {
38 | type: String,
39 | },
40 |
41 | address: {
42 | type: String,
43 | },
44 |
45 | alert_type: {
46 | type: String,
47 | enum: ['Perdido', 'Abandonado', 'Adopción', 'Crítico'],
48 | required: true
49 | },
50 |
51 | email: {
52 | type: String
53 | },
54 |
55 | phone: {
56 | type: String
57 | },
58 |
59 | description: {
60 | type: String
61 | }
62 | })
63 |
64 | export var AlertModel = mongoose.model('alert', AlertSchema)
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/Spinner/Spinner.css:
--------------------------------------------------------------------------------
1 | .loader,
2 | .loader:before,
3 | .loader:after {
4 | border-radius: 50%;
5 | width: 2.5em;
6 | height: 2.5em;
7 | -webkit-animation-fill-mode: both;
8 | animation-fill-mode: both;
9 | -webkit-animation: load7 1.8s infinite ease-in-out;
10 | animation: load7 1.8s infinite ease-in-out;
11 | }
12 | .loader {
13 | color: #e27e22;
14 | font-size: 10px;
15 | margin: 80px auto;
16 | position: relative;
17 | text-indent: -9999em;
18 | -webkit-transform: translateZ(0);
19 | -ms-transform: translateZ(0);
20 | transform: translateZ(0);
21 | -webkit-animation-delay: -0.16s;
22 | animation-delay: -0.16s;
23 | }
24 | .loader:before,
25 | .loader:after {
26 | content: "";
27 | position: absolute;
28 | top: 0;
29 | }
30 | .loader:before {
31 | left: -3.5em;
32 | -webkit-animation-delay: -0.32s;
33 | animation-delay: -0.32s;
34 | }
35 | .loader:after {
36 | left: 3.5em;
37 | }
38 | @-webkit-keyframes load7 {
39 | 0%,
40 | 80%,
41 | 100% {
42 | box-shadow: 0 2.5em 0 -1.3em;
43 | }
44 | 40% {
45 | box-shadow: 0 2.5em 0 0;
46 | }
47 | }
48 | @keyframes load7 {
49 | 0%,
50 | 80%,
51 | 100% {
52 | box-shadow: 0 2.5em 0 -1.3em;
53 | }
54 | 40% {
55 | box-shadow: 0 2.5em 0 0;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "to-the-rescue-api",
3 | "version": "1.0.0",
4 | "description": "An API for the \"Al Rescate\" website that aims to be a centralized report system for endangered animals across Cuba",
5 | "main": "index.js",
6 | "repository": "https://github.com/josejorgers/to-the-rescue",
7 | "author": "Jose Jorge Rodriguez Salgado ",
8 | "license": "MIT",
9 | "private": false,
10 | "type": "module",
11 | "dependencies": {
12 | "body-parser": "^1.19.0",
13 | "cors": "^2.8.5",
14 | "deta": "^1.0.0",
15 | "dotenv": "^8.2.0",
16 | "express": "^4.17.1",
17 | "moment": "^2.29.1",
18 | "mongodb": "^3.6.6",
19 | "mongoose": "^5.12.4",
20 | "random-token": "^0.0.8",
21 | "serve-static": "^1.14.1"
22 | },
23 | "devDependencies": {
24 | "axios": "^0.21.1",
25 | "chai": "^4.3.4",
26 | "chai-http": "^4.3.0",
27 | "mocha": "^8.3.2"
28 | },
29 | "scripts": {
30 | "test": "mocha --recursive --exit --timeout 5000",
31 | "start": "node index.js"
32 | },
33 | "compilerOptions": {
34 | "target": "esnext",
35 | "module": "es2015",
36 | "allowSyntheticDefaultImports": true,
37 | "experimentalDecorators": true
38 | },
39 | "engines": {
40 | "node": "14.x"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/Components/Posts/Post/Post.css:
--------------------------------------------------------------------------------
1 | .Post {
2 | width: 100%;
3 | height: 75px;
4 | }
5 | .Post img {
6 | height: 84px;
7 | }
8 | .PostCard {
9 | border-radius: 10px 10px 0px 10px;
10 | box-shadow: 0 4px 2px -2px rgba(0, 0, 0, 0.2);
11 | }
12 | .PostImageDiv {
13 | width: 36%;
14 | }
15 |
16 | .PostWidth {
17 | width: 100%;
18 | }
19 |
20 | .PostHeight {
21 | height: 100%;
22 | }
23 | .PostXSmall {
24 | font-size: x-small;
25 | }
26 |
27 | .PostSmall {
28 | font-size: small;
29 | }
30 | .PostBold {
31 | font-weight: bold;
32 | }
33 |
34 | .Adopción {
35 | color: #2ec871;
36 | font-weight: bold;
37 | font-family: Verdana, Geneva, Tahoma, sans-serif;
38 | }
39 | .Perdido {
40 | color: #edc00f;
41 | font-weight: bold;
42 | font-family: Verdana, Geneva, Tahoma, sans-serif;
43 | }
44 | .Abandonado {
45 | color: #e27e22;
46 | font-weight: bold;
47 | font-family: Verdana, Geneva, Tahoma, sans-serif;
48 | }
49 | .Crítico {
50 | color: #e34c3c;
51 | font-weight: bold;
52 | font-family: Verdana, Geneva, Tahoma, sans-serif;
53 | }
54 |
55 | .post-image-preview {
56 | height: 76px !important;
57 | width: 105px !important;
58 | border-radius: 10px 0px 0px 10px !important ;
59 | }
60 |
61 | .post-link {
62 | color: #464646 !important;
63 | text-decoration: none !important;
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/src/Components/Flash/Flash.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Toast } from 'react-bootstrap';
3 | import Bus from '../../Utils/Bus';
4 | import "./Flash.css"
5 |
6 | function Flash() {
7 | let [visibility, setVisibility] = useState(false);
8 | let [message, setMessage] = useState('');
9 | let [type, setType] = useState('');
10 |
11 | useEffect(() => {
12 | Bus.addListener('flash', ({ message, type }) => {
13 | setVisibility(true);
14 | setMessage(message);
15 | setType(type);
16 | });
17 |
18 |
19 | }, []);
20 | const toast = (
21 |
22 | setVisibility(false)}
31 | show={visibility}
32 | delay={3000}
33 | autohide
34 | >
35 | {message}
36 |
37 |
38 | )
39 | return (
40 | visibility && toast
41 | )
42 | }
43 | export default Flash
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tribilin",
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 | "browser-image-compression": "^1.0.15",
10 | "deta": "^1.0.0",
11 | "events": "^3.3.0",
12 | "lzjs": "^1.3.0",
13 | "query-string": "^7.0.1",
14 | "react": "^17.0.2",
15 | "react-bootstrap": "^1.6.0",
16 | "react-dom": "^17.0.2",
17 | "react-icons": "^4.2.0",
18 | "react-redux": "^7.2.4",
19 | "react-router": "^5.2.0",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "4.0.3",
22 | "redux": "^4.1.0",
23 | "redux-persist": "^6.0.0",
24 | "web-vitals": "^1.0.1"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/src/Components/TopHeader/TopHeader.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import "./TopHeader.css"
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { LOG_OUT } from "../../store/actions";
5 | import UserIcon from "../Layout/UserIcon/UserIcon";
6 | import MenuIcon from "../Layout/MenuIcon/MenuIcon";
7 | import { useHistory } from "react-router";
8 |
9 | function TopHeader({ title, smallTitle, goHome, showAll }) {
10 | const dispatch = useDispatch()
11 | const userName = useSelector((state) => state.userName)
12 | const history = useHistory()
13 | return (
14 |
15 |
16 |
{title}
17 |
18 | {
19 | dispatch({ type: LOG_OUT })
20 | history.push("/")
21 | window.flash("Ha cerrado sesión correctamente", "success")
22 | }} />
23 |
24 |
25 |
26 |
27 |
{smallTitle}
28 |
29 | )
30 | }
31 |
32 | export default TopHeader
--------------------------------------------------------------------------------
/frontend/src/Components/NewPost/NewPost.css:
--------------------------------------------------------------------------------
1 | .NewPostImage {
2 | width: 200px;
3 | height: 200px;
4 | border-radius: 10px;
5 | margin-bottom: 25px;
6 | }
7 |
8 | .NewPostDropdown {
9 | width: 200px;
10 | height: 25px;
11 | border-radius: 5px;
12 | background-color: white;
13 | margin-bottom: 15px;
14 | }
15 |
16 | .NewPostDropdownItems {
17 | font-family: Verdana, Geneva, Tahoma, sans-serif;
18 | font-weight: bold;
19 | font-size: small;
20 | }
21 |
22 | .NewPostInput {
23 | width: 200px;
24 | height: 25px;
25 | border-radius: 5px;
26 | background-color: white;
27 | margin-bottom: 15px;
28 | border: solid 1px gray;
29 | }
30 |
31 | /* customContainer needed to position the button. Adjust the width as needed */
32 | .customContainer {
33 | position: relative;
34 | /* width: 50%; */
35 | }
36 |
37 | /* Make the image responsive */
38 | /* .customContainer img {
39 | width: 100%;
40 | height: auto;
41 | } */
42 |
43 | /* Style the button and place it in the middle of the customContainer/image */
44 | .customContainer label {
45 | position: absolute;
46 | top: 75%;
47 | left: 70%;
48 | transform: translate(-50%, -50%);
49 | -ms-transform: translate(-50%, -50%);
50 | background-color: #555;
51 | color: white;
52 | font-size: 16px;
53 | border: none;
54 | cursor: pointer;
55 | width: 50px;
56 | height: 50px;
57 | border-radius: 50px;
58 | }
59 |
60 | .customContainer label:hover {
61 | background-color: black;
62 | }
63 |
--------------------------------------------------------------------------------
/backend/managers/alert-manager.js:
--------------------------------------------------------------------------------
1 | import { AlertModel } from "../models/alert.js";
2 | import Repository from "../repositories/repository.js";
3 | import Manager from "./manager.js";
4 | import randomToken from "random-token";
5 | import moment from "moment";
6 | import detaPackage from "deta";
7 | const { Deta } = detaPackage;
8 | import { TOY_DETA_KEY } from "../config/constant.js";
9 |
10 | class AlertManager extends Manager{
11 |
12 | constructor(){
13 | super(new Repository(AlertModel))
14 | }
15 |
16 | async insert(alert) {
17 |
18 | if(!alert.imgString)
19 | return Manager.prototype.insert.call(this, alert)
20 |
21 | const date = moment().format('YYYYMMDD')
22 | const middle = `${alert.alert_type}-${alert.province}-${alert.municipality}-`
23 | const random = randomToken(5)
24 | const extension = '.jpg'
25 |
26 | const picName = date + middle + random + extension
27 |
28 | const alertToStore = {
29 | ...alert,
30 | picture_path: picName
31 | }
32 |
33 | const imageToStore = alert.imgString.split(';base64,').pop()
34 |
35 | const deta = Deta(process.env.DETA_KEY || TOY_DETA_KEY)
36 |
37 | const photos = deta.Drive(process.env.PHOTOS_DRIVE_NAME || 'photos')
38 |
39 | await photos.put(picName, { data: imageToStore })
40 |
41 |
42 | return Manager.prototype.insert.call(this, alertToStore)
43 | }
44 |
45 | }
46 |
47 | export default AlertManager
--------------------------------------------------------------------------------
/backend/repositories/repository.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // TODO: Add logging
4 | class Repository{
5 |
6 | constructor(model){
7 | this.model = model
8 | }
9 |
10 | create(entry){
11 | return this.model.create(entry)
12 | }
13 |
14 | find(filters){
15 | return this.run([{method: 'find', args: [filters]}])
16 | }
17 |
18 | select(filters, fields){
19 | return this.run([{
20 | method: 'find',
21 | args: [
22 | filters,
23 | fields
24 | ]
25 | }])
26 | }
27 |
28 | remove(filters){
29 | return this.run([
30 | {
31 | method: 'find',
32 | args: [filters]
33 | },
34 | {
35 | method: 'deleteMany',
36 | args: []
37 | }
38 | ])
39 | }
40 |
41 | update(filters, newFields){
42 | return this.run([
43 | {
44 | method: 'find',
45 | args: [filters]
46 | },
47 | {
48 | method: 'updateMany',
49 | args: [newFields]
50 | }
51 | ])
52 | }
53 |
54 | run(queries, callback){
55 |
56 | var result = this.model
57 | for (const q of queries) {
58 | const method = q.method
59 | const args = q.args
60 |
61 | result = result[method](...args)
62 | }
63 |
64 | return result.exec(callback)
65 | }
66 | }
67 |
68 | export default Repository
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import React, { Component } from "react";
3 | import Posts from "./Components/Posts/Posts";
4 | import NewPost from "./Components/NewPost/NewPost";
5 | import PostDetails from "./Components/PostDetails/PostDetails";
6 | import { Route } from "react-router-dom";
7 | import Register from "./Components/Authentication/Register";
8 | import Login from "./Components/Authentication/Login";
9 | import MyPosts from "./Components/MyPosts/MyPosts";
10 | import EditPost from "./Components/EditPost/EditPost";
11 | import Flash from "./Components/Flash/Flash";
12 | import Bus from "./Utils/Bus";
13 | import AboutUs from "./Components/AboutUs/AboutUs";
14 |
15 | class App extends Component {
16 | render() {
17 | window.flash = (message, type = "success") =>
18 | Bus.emit("flash", { message, type });
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 | }
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/push-notification-test/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | // Give the service worker access to Firebase Messaging.
2 | // Note that you can only use Firebase Messaging here. Other Firebase libraries
3 | // are not available in the service worker.
4 | importScripts('https://www.gstatic.com/firebasejs/8.4.2/firebase-app.js');
5 | importScripts('https://www.gstatic.com/firebasejs/8.4.2/firebase-messaging.js');
6 |
7 | // Initialize the Firebase app in the service worker by passing in
8 | // your app's Firebase config object.
9 | // https://firebase.google.com/docs/web/setup#config-object
10 | firebase.initializeApp({
11 | apiKey: "AIzaSyCOl76atKnpcjHsrK6MknUXRBekDK_dJl8",
12 | authDomain: "to-the-rescue-4c250.firebaseapp.com",
13 | projectId: "to-the-rescue-4c250",
14 | storageBucket: "to-the-rescue-4c250.appspot.com",
15 | messagingSenderId: "88808042476",
16 | appId: "1:88808042476:web:b3d36e2fff4b43c5c05ac9",
17 | measurementId: "G-SE04L5SX65"
18 | });
19 |
20 | // Retrieve an instance of Firebase Messaging so that it can handle background
21 | // messages.
22 | const messaging = firebase.messaging();
23 |
24 |
25 | messaging.onBackgroundMessage((payload) => {
26 | console.log('[firebase-messaging-sw.js] Received background message ', payload);
27 | // Customize notification here
28 | const notificationTitle = payload.notification.title;
29 | const notificationOptions = {
30 | body: payload.notification.body,
31 | // icon: '/firebase-logo.png'
32 | };
33 |
34 | self.registration.showNotification(notificationTitle,
35 | notificationOptions);
36 | });
--------------------------------------------------------------------------------
/frontend/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from "./actions"
2 |
3 | const initialState = {
4 | posts: [],
5 | selectedPost: {},
6 | newPost: {
7 | id: "",
8 | title: "",
9 | body: ""
10 | },
11 | showSideDrawer: false,
12 | userId: null,
13 | userEmail: null,
14 | userName: null
15 | }
16 | const reducer = (state = initialState, action) => {
17 | switch (action.type) {
18 | case actionTypes.POPULATE_POSTS:
19 | return {
20 | ...state,
21 | posts: action.newPosts
22 | }
23 | case actionTypes.POPULATE_SELECTED_POST:
24 | return {
25 | ...state,
26 | selectedPost: action.newPost
27 | }
28 | case actionTypes.SET_USER_ID:
29 | console.log(action.userId)
30 | return {
31 | ...state,
32 | userId: action.userId,
33 | userEmail: action.userEmail,
34 | userName: action.userName
35 | }
36 | case actionTypes.LOG_OUT:
37 | return {
38 | ...state,
39 | selectedPost: {},
40 | newPost: {
41 | id: "",
42 | title: "",
43 | body: ""
44 | },
45 | showSideDrawer: false,
46 | userId: null,
47 | userEmail: null,
48 | userName: null
49 | }
50 | default:
51 | return state
52 | }
53 | }
54 |
55 | export default reducer
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 | import Layout from './Components/Layout/Layout';
7 | import { BrowserRouter } from 'react-router-dom';
8 | import { createStore } from "redux"
9 | import reducer from "./store/reducer"
10 | import { Provider } from "react-redux"
11 | import { persistReducer, persistStore } from 'redux-persist';
12 | import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
13 | import { PersistGate } from 'redux-persist/integration/react';
14 |
15 | const persistConfig = {
16 | key: 'root',
17 | storage: storage,
18 | whitelist: ['userId', 'userEmail', 'userName'] // only these props will be persisted
19 | };
20 |
21 | const persistedReducer = persistReducer(persistConfig, reducer)
22 | let store = createStore(persistedReducer)
23 | let persistor = persistStore(store)
24 |
25 | ReactDOM.render(
26 |
27 |
28 |
29 | Loading...} persistor={persistor}>
30 |
31 |
32 |
33 |
34 |
35 |
36 | ,
37 |
38 | document.getElementById('root')
39 | );
40 |
41 | // If you want to start measuring performance in your app, pass a function
42 | // to log results (for example: reportWebVitals(console.log))
43 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
44 | reportWebVitals();
45 |
--------------------------------------------------------------------------------
/backend/test/data-access/user.js:
--------------------------------------------------------------------------------
1 | import chai from "chai"
2 | import UserManager from "../../managers/user-manager.js"
3 | import { dummyUser } from "../data/data.js"
4 |
5 | const should = chai.should()
6 |
7 | context('Testing Data access for Users', () => {
8 |
9 | const userManager = new UserManager()
10 |
11 | describe('CRUD for Users', () => {
12 |
13 | it('Should store the user without errors', async () => {
14 |
15 | await userManager.insert(dummyUser)
16 | })
17 |
18 | it('Should retrieve the user', async () => {
19 |
20 | var user = (await userManager.find({firstName: 'John'}))[0]
21 | user.should.have.property('lastName').equal(dummyUser.lastName)
22 | })
23 |
24 | it('Should update the user', async () => {
25 |
26 | await userManager.update({firstName: 'John'}, {lastName: 'Wall'})
27 |
28 | const user = (await userManager.find({firstName: 'John'}))[0]
29 |
30 | user.lastName.should.be.equal('Wall')
31 | })
32 |
33 | it('Should delete the user', async () => {
34 |
35 | await userManager.delete({firstName: 'John'})
36 |
37 | const user = await userManager.find({firstName: 'John'})
38 |
39 | user.length.should.be.eq(0)
40 | })
41 | })
42 |
43 | describe('Testing if the effect of an extra token parameter', () => {
44 |
45 | it('Should insert the new user with the extra parameter without errors', async () => {
46 |
47 | userManager.insert({
48 | ...dummyUser,
49 | lastName: 'Wall',
50 | secondLastName: 'WithToken',
51 | token: 'qwerty'
52 | })
53 |
54 | })
55 | })
56 | })
--------------------------------------------------------------------------------
/backend/test/endpoints/token.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai'
2 | import chaiHttp from 'chai-http'
3 |
4 | import app from "../../index.js"
5 |
6 | const should = chai.should();
7 |
8 |
9 | chai.use(chaiHttp)
10 |
11 | context('Test the token endpoints of the api', () => {
12 | const TOKEN = 'qwerty'
13 |
14 | describe('Basic CRUD endpoints', () => {
15 | var tokenId = null
16 |
17 | it('Should store the token without errors', (done) => {
18 |
19 | chai.request(app)
20 | .post("/token")
21 | .send({token: TOKEN})
22 | .end((err, res) => {
23 | res.should.have.status(200)
24 | res.body.should.be.a('object')
25 | res.body.token.should.be.equal(TOKEN)
26 | tokenId = res.body._id
27 | done()
28 | })
29 | })
30 |
31 | it('Should update the token', (done) => {
32 |
33 | chai.request(app)
34 | .put('/token')
35 | .send({
36 | token: TOKEN,
37 | user_id: 'some-random-id'
38 | })
39 | .end((err, res) => {
40 | res.should.have.status(200)
41 | res.body.should.be.a('object')
42 | res.body.n.should.be.equal(1)
43 | res.body.nModified.should.be.equal(1)
44 | done()
45 | })
46 | })
47 |
48 | it('Should not update unexistent token', (done) => {
49 |
50 |
51 | chai.request(app)
52 | .put('/token')
53 | .send({
54 | token: 'UNEXISTENT-TOKEN',
55 | user_id: 'user id'
56 | })
57 | .end((err, res) => {
58 | res.body.nModified.should.be.equal(0)
59 | done()
60 | })
61 | })
62 | })
63 |
64 | })
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *node_modules
2 | build
3 |
4 | *.jpg
5 |
6 | # Logs
7 | *logs
8 | *.log
9 | *npm-debug.log*
10 | *yarn-debug.log*
11 | *yarn-error.log*
12 | *lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | *report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | *pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | *lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | *coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | *.nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | *.grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | *bower_components
38 |
39 | # node-waf configuration
40 | *.lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | *build/Release
44 |
45 | # Dependency directories
46 | *node_modules/
47 | *jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | *typings/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | *.npm
57 |
58 | # Optional eslint cache
59 | *.eslintcache
60 |
61 | # Microbundle cache
62 | *.rpt2_cache/
63 | *.rts2_cache_cjs/
64 | *.rts2_cache_es/
65 | *.rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | *.node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | *.yarn-integrity
75 |
76 | # dotenv environment variables file
77 | *.env
78 | *.env.test
79 |
80 | # parcel-bundler cache (https://parceljs.org/)
81 | *.cache
82 |
83 | # Next.js build output
84 | *.next
85 |
86 | # Nuxt.js build / generate output
87 | *.nuxt
88 | *dist
89 |
90 | # Gatsby files
91 | *.cache/
92 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
93 | # https://nextjs.org/blog/next-9-1#public-directory-support
94 | # public
95 |
96 | # vuepress build output
97 | *.vuepress/dist
98 |
99 | # Serverless directories
100 | *.serverless/
101 |
102 | # FuseBox cache
103 | *.fusebox/
104 |
105 | # DynamoDB Local files
106 | *.dynamodb/
107 |
108 | # TernJS port file
109 | *.tern-port
110 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
26 | Tribilin
27 |
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/UserIcon/UserIcon.js:
--------------------------------------------------------------------------------
1 | import { Dropdown } from "react-bootstrap"
2 | import React from "react"
3 | import { IoLogOut, IoLogIn, IoPersonAdd } from "react-icons/io5";
4 | import { Link } from "react-router-dom";
5 |
6 |
7 | function UserIcon({ userName, logOutHandler }) {
8 | const CustomToggle = React.forwardRef(({ onClick }, ref) => (
9 | {
11 | e.preventDefault();
12 | onClick(e);
13 | }} />
14 | ));
15 | return (
16 |
17 |
18 | Custom Toggle
19 |
20 | {userName ?
21 |
22 | Hola {userName}
23 |
24 |
25 | Cerrar sesión
26 |
27 |
28 | :
29 |
30 |
31 |
32 |
33 | Iniciar sesión
34 |
35 |
36 |
37 |
38 |
39 | Registrarse
40 |
41 |
42 |
43 | }
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | export default UserIcon
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/test/endpoints/user.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai'
2 | import chaiHttp from 'chai-http'
3 |
4 | import app from "../../index.js"
5 | import { dummyUser } from "../data/data.js";
6 |
7 | const should = chai.should();
8 |
9 |
10 | chai.use(chaiHttp)
11 |
12 | context('Test the user endpoints of the api', () => {
13 |
14 | describe('Basic CRUD endpoints', () => {
15 | var userId = null
16 |
17 | it('Should store the user without errors', (done) => {
18 |
19 | chai.request(app)
20 | .post("/user")
21 | .send(dummyUser)
22 | .end((err, res) => {
23 | res.should.have.status(200)
24 | res.body.should.be.a('object')
25 | res.body.firstName.should.be.equal(dummyUser.firstName)
26 | userId = res.body._id
27 | done()
28 | })
29 | })
30 |
31 | it('Should retrieve the user', (done) => {
32 |
33 | chai.request(app)
34 | .get('/user/' + userId)
35 | .end((err, res) => {
36 | res.should.have.status(200)
37 | res.body.should.be.a('object')
38 | res.body.firstName.should.be.equal(dummyUser.firstName)
39 | done()
40 | })
41 | })
42 |
43 | it('Should update the user', (done) => {
44 |
45 | chai.request(app)
46 | .put('/user/' + userId)
47 | .send({
48 | lastName: 'Wall'
49 | })
50 | .end(() => {
51 | chai.request(app)
52 | .get('/user/' + userId)
53 | .end((err, res) => {
54 | res.should.have.status(200)
55 | res.body.should.be.a('object')
56 | res.body.lastName.should.be.equal('Wall')
57 | done()
58 | })
59 | })
60 | })
61 |
62 | it('Should delete the user', (done) => {
63 |
64 |
65 | chai.request(app)
66 | .delete('/user/' + userId)
67 | .end(() => {
68 | chai.request(app)
69 | .get('/user/' + userId)
70 | .end((err, res) => {
71 | res.should.have.status(200)
72 | should.equal(res.body, null)
73 | done()
74 | })
75 | })
76 | })
77 | })
78 |
79 | })
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/PostsTabs/PostsTabs.js:
--------------------------------------------------------------------------------
1 | import { Tabs, Tab } from "react-bootstrap";
2 | import { Link, useLocation } from "react-router-dom";
3 | import { IoPaw } from "react-icons/io5";
4 | import { FaHandHoldingHeart, FaHouseDamage } from "react-icons/fa";
5 | import { SiOpenstreetmap } from "react-icons/si";
6 | import { BsExclamationTriangleFill } from "react-icons/bs";
7 | import { useRef, useEffect } from "react";
8 |
9 | function PostsTabs({ body, activeKey, search }) {
10 | const location = useLocation()
11 | const searchParams = useRef("")
12 |
13 |
14 | useEffect(() => {
15 | searchParams.current = new URLSearchParams(location.search)
16 | if (searchParams.current.has("alert_type"))
17 | searchParams.current.delete("alert_type")
18 |
19 |
20 | })
21 | return (
22 |
27 |
29 |
30 |
31 | } >
32 | {body}
33 |
34 |
38 |
39 |
40 | }
41 | >
42 | {body}
43 |
44 |
48 |
49 |
50 | }>
51 | {body}
52 |
53 |
57 |
58 |
59 | }>
60 | {body}
61 |
62 |
66 |
67 |
68 | }>
69 | {body}
70 |
71 |
72 | )
73 | }
74 | export default PostsTabs
--------------------------------------------------------------------------------
/frontend/src/Components/MyPosts/MyPosts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import { Link } from "react-router-dom"
3 | import { connect } from "react-redux"
4 | import { POPULATE_POSTS } from "../../store/actions"
5 | import TopHeader from "../TopHeader/TopHeader"
6 | import { SERVER_URL } from "../../Constants/constants"
7 | import Spinner from "../Layout/Spinner/Spinner"
8 | import Post from "../Posts/Post/Post"
9 | import Footer from "../Footer/Footer"
10 |
11 | class MyPosts extends Component {
12 | state = {
13 | loading: true,
14 | }
15 | componentDidMount() {
16 | fetch(SERVER_URL + "alert-by-user/" + this.props.userId)
17 | .then(response => response.json())
18 | .then(data => {
19 | console.log(data)
20 | this.props.populatePosts(data);
21 | this.setState({ loading: false })
22 | })
23 | .catch(error => {
24 | this.setState({ loading: false })
25 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
26 | })
27 | }
28 |
29 | render() {
30 | const posts = (this.props.posts || [])
31 | .map(post =>
43 | )
44 |
45 | if (this.state.loading)
46 | return
47 | return (
48 |
49 |
50 |
51 | {
52 | posts.length > 0 ?
53 | posts :
54 |
No ha publicado alertas.
55 | }
56 |
57 |
58 |
59 | );
60 | }
61 | }
62 | const mapStateToProps = state => {
63 | return {
64 | posts: state.posts,
65 | userId: state.userId
66 | }
67 | }
68 | const mapDispatchToProps = dispatch => {
69 | return {
70 | populatePosts: (data) => dispatch({ type: POPULATE_POSTS, newPosts: data })
71 | }
72 | }
73 | export default connect(mapStateToProps, mapDispatchToProps)(MyPosts);
--------------------------------------------------------------------------------
/backend/test/endpoints/alert.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai'
2 | import chaiHttp from 'chai-http'
3 |
4 | import app from "../../index.js"
5 | import { dummyAlert } from '../data/data.js';
6 |
7 | const should = chai.should();
8 |
9 |
10 | chai.use(chaiHttp)
11 |
12 | context('Test the alert endpoints of the api', () => {
13 |
14 | describe('Basic CRUD endpoints', () => {
15 | var alertId = null
16 |
17 | it('Should store the alert without errors', (done) => {
18 |
19 | chai.request(app)
20 | .post("/alert")
21 | .send(dummyAlert)
22 | .end((err, res) => {
23 | res.should.have.status(200)
24 | res.body.should.be.a('object')
25 | res.body.author_id.should.be.equal(dummyAlert.author_id)
26 | alertId = res.body._id
27 | done()
28 | })
29 | })
30 |
31 | it('Should retrieve the alert', (done) => {
32 |
33 | chai.request(app)
34 | .get('/alert/' + alertId)
35 | .end((err, res) => {
36 | res.should.have.status(200)
37 | res.body.should.be.a('object')
38 | res.body.municipality.should.be.equal(dummyAlert.municipality)
39 | done()
40 | })
41 | })
42 |
43 | it('Should update the alert', (done) => {
44 |
45 | chai.request(app)
46 | .put('/alert/' + alertId)
47 | .send({
48 | alert_type: 'Abandonado'
49 | })
50 | .end(() => {
51 | chai.request(app)
52 | .get('/alert/' + alertId)
53 | .end((err, res) => {
54 | res.should.have.status(200)
55 | res.body.should.be.a('object')
56 | res.body.alert_type.should.be.equal('Abandonado')
57 | done()
58 | })
59 | })
60 | })
61 |
62 | it('Should delete the alert', (done) => {
63 |
64 |
65 | chai.request(app)
66 | .delete('/alert/' + alertId)
67 | .end(() => {
68 | chai.request(app)
69 | .get('/alert/' + alertId)
70 | .end((err, res) => {
71 | res.should.have.status(200)
72 | res.body.should.be.a('string')
73 | res.body.length.should.be.equal(0)
74 | done()
75 | })
76 | })
77 | })
78 | })
79 |
80 | })
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/MenuIcon/MenuIcon.js:
--------------------------------------------------------------------------------
1 | import { Fragment } from "react"
2 | import { Dropdown } from "react-bootstrap";
3 | import React from "react"
4 | import { IoInformationCircle } from "react-icons/io5"
5 | import { MdFavorite } from "react-icons/md"
6 | import { GiDogHouse } from "react-icons/gi"
7 | import { Link } from "react-router-dom";
8 |
9 | function MenuIcon({ authenticated, goHome, showAll }) {
10 | const CustomToggle = React.forwardRef(({ onClick }, ref) => (
11 | {
13 | e.preventDefault();
14 | onClick(e);
15 | }} />
16 | ));
17 | return (
18 |
19 |
20 | Custom Toggle
21 |
22 | {
23 | goHome &&
24 |
25 |
26 |
27 | Volver al inicio
28 |
29 |
30 | }
31 | {
32 | authenticated && (!goHome || showAll) ?
33 |
34 |
35 |
36 | Mis Alertas
37 |
38 | :
39 | null
40 | }
41 | {
42 | window.location.href.includes("/about-us") ?
43 | null :
44 |
45 |
46 |
47 | Sobre nosotros
48 |
49 |
50 | }
51 |
52 |
53 |
54 | )
55 | }
56 | export default MenuIcon
--------------------------------------------------------------------------------
/frontend/src/Components/Authentication/Login.js:
--------------------------------------------------------------------------------
1 | import { Card, Form, Button } from "react-bootstrap"
2 | import { useState } from "react"
3 | import { SERVER_URL } from "../../Constants/constants"
4 | import { Redirect } from "react-router"
5 | import { useDispatch } from "react-redux"
6 | import { SET_USER_ID } from "../../store/actions"
7 | import { Link } from "react-router-dom"
8 |
9 | function Login() {
10 | const [firstName, setName] = useState("")
11 | const [email, setEmail] = useState("")
12 | const [validated, setValidated] = useState(false)
13 | const [redirect, setRedirect] = useState(false)
14 | const dispatch = useDispatch()
15 |
16 | const submitHandler = (event) => {
17 | event.preventDefault();
18 |
19 | const form = event.currentTarget;
20 | if (form.checkValidity() === false) {
21 | event.stopPropagation();
22 | setValidated(true);
23 | return
24 | }
25 | const request = {
26 | method: "POST",
27 | headers: { "Content-Type": "application/json" },
28 | body: JSON.stringify({
29 | email: email,
30 | firstName: firstName
31 | })
32 | }
33 | fetch(SERVER_URL + "user-login", request)
34 | .then(response => response.json())
35 | .then(response => {
36 | dispatch({
37 | type: SET_USER_ID,
38 | userId: response._id,
39 | userName: response.firstName,
40 | userEmail: response.email
41 | })
42 | setRedirect(true)
43 | window.flash("Ha iniciado sesión correctamente", "success")
44 | })
45 | .catch(error => window.flash("Nombre de usuario o email incorrectos", "error"))
46 | }
47 | if (redirect)
48 | return
49 |
50 | return (
51 |
52 |
53 |
55 | setName(event.target.value)} />
56 |
57 |
58 | setEmail(event.target.value)} />
59 | Ingrese Correo Electronico
60 |
61 |
62 | Cancelar
63 | Iniciar Sesión
64 |
65 |
66 |
67 |
68 |
69 | )
70 | }
71 |
72 | export default Login
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tribilin
2 |
3 | A project to help vulnerable animals in Cuba - just in Cuba so far.
4 |
5 | ## Website
6 |
7 | Here we have implemented our website called **Tribilin**. It is a system to publish alerts about adoptions, animals in the street in critical condition, lost animals, etc.
8 | We hope we can make a powerful tool to help animals out of **Tribilin** and with the help of the developer community.
9 |
10 | You can view the production website [here](https://tribilin.netlify.app)
11 |
12 | ## Tools used
13 |
14 | Everything has been done using JavaScript (React + Express). We also use Deta to store pictures in the cloud. We are open to adopt as many different languages and technologies as
15 | needed.
16 |
17 | ## Project organization
18 |
19 | This is a monorepo. The idea is that all the projects can be added here. So we think we need to make some clarifications about organization of this repo
20 |
21 | ### Branching
22 |
23 | We have three important branches:
24 |
25 | * ```main```: here we have the last stable version of the website. This branch is only updated by merging pull-requests from ```master```
26 | * ```master```: here we have the last version of the website. This is the staging version that we need to test before declaring it stable. This branch is only updated by merging
27 | pull-requests from ```dev```
28 | * ```dev```: the development version. This is the default branch and the first place when the accepted contributions go to.
29 |
30 | The rest of the branches are created by contributors. To learn about branches naming conventions in this project, please read the ```CONTRIBUTING.md``` document.
31 |
32 | ### Projects
33 |
34 | So far we have only the frontend and the backend of the website. Any upcoming project would be placed at the same level of those. To run both projects you just need to follow
35 | the these steps. We assume you have ```Node.js``` and ```yarn``` installed in your local environment.
36 |
37 | * Clone this repo. You'll be cloning the ```dev``` branch
38 | * Go to the ```frontend```/```backend``` directory (whatever you want to run)
39 | * Run ```yarn```
40 | * After dependencies have been installed run ```yarn start```
41 |
42 | This also asumes you have a MongoDB server in your local environment and that it is running in the default url (_mongodb://localhost:27017_).
43 |
44 | With both frontend and backend running you'll have the website running in your local environment!
45 |
46 | ### Running tests
47 |
48 | The backend project is provided with unit tests. To see those test results you can run ```yarn test``` in the ```backend``` directory.
49 |
50 | ## Contributing
51 |
52 | We encourage all developers to contribute with this project. Any help will be very welcome. Just make sure to read the ```CONTRIBUTING.md``` document where we give the instructions to make organized and fruitful contributions.
53 |
54 | If you are not a developer but want to help with promotion or anything else, please reach us through our social media pages that you can find in the [about us page](https://tribilin.netlify.app/about-us) in **Tribilin**
55 |
--------------------------------------------------------------------------------
/frontend/src/Components/Posts/Post/Post.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "./Post.css"
3 | import { Date } from "core-js";
4 | import { Card, Spinner } from "react-bootstrap";
5 | import { DETA_API_KEY, DETA_PROJECT_ID, DETA_URL, TOY_DETA_ID, TOY_DETA_KEY } from "../../../Constants/constants";
6 | import { Link } from "react-router-dom";
7 |
8 | function Post({ id, animal, alert_type, municipality, date, description, from, picture_path, province }) {
9 | if (description === "")
10 | description = `${animal}:${alert_type}`
11 |
12 | let d = new Date(date)
13 | const formattedDate = d.toLocaleDateString("es-ES")
14 | const preview = description.substring(0, 35) + "..."
15 | const detaDriveName = 'photos'
16 | const projectId = DETA_PROJECT_ID || TOY_DETA_ID
17 | const urlSuffix = `files/download?name=${picture_path}`
18 |
19 | const imgUrl = picture_path === "" ? "/default.png" : `${DETA_URL}${projectId}/${detaDriveName}/${urlSuffix}`
20 |
21 | const [img, setImg] = useState(null)
22 |
23 | // Fetch the image using the imgUrl and set the state. Add authotization headers
24 | useEffect(() => {
25 | if (imgUrl !== "/default.png") {
26 | fetch(imgUrl, { headers: { "X-Api-Key": DETA_API_KEY || TOY_DETA_KEY } })
27 | .then(response => response.text())
28 | .then(text => {
29 | const pict = "data:image/png;base64," + text
30 | setImg(pict)
31 | })
32 | }
33 | else
34 | setImg(imgUrl)
35 | }, [imgUrl])
36 |
37 | return (
38 |
39 |
40 |
44 |
45 | {
46 | !img ?
47 |
:
48 |
53 | }
54 |
55 |
56 |
57 |
{alert_type}
58 |
{municipality || province}
59 |
60 |
{preview}
61 |
{formattedDate}
62 |
63 |
64 |
65 |
66 | );
67 | }
68 |
69 | export default Post;
--------------------------------------------------------------------------------
/push-notification-test/firebase-script.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
27 |
47 |
48 |
67 |
68 |
--------------------------------------------------------------------------------
/frontend/src/Components/Authentication/Register.js:
--------------------------------------------------------------------------------
1 | import { Card, Form, Button } from "react-bootstrap"
2 | import { useState } from "react"
3 | import { SERVER_URL } from "../../Constants/constants"
4 | import { Redirect } from "react-router"
5 | import { useDispatch } from "react-redux"
6 | import { SET_USER_ID } from "../../store/actions"
7 | import { Link } from "react-router-dom"
8 |
9 | function Register() {
10 | const [firstName, setName] = useState("")
11 | const [email, setEmail] = useState("")
12 | const [validated, setValidated] = useState(false)
13 | const [redirect, setRedirect] = useState(false)
14 | const dispatch = useDispatch()
15 |
16 | const submitHandler = (event) => {
17 | event.preventDefault();
18 |
19 | const form = event.currentTarget;
20 | if (form.checkValidity() === false) {
21 | event.stopPropagation();
22 | setValidated(true);
23 | return
24 | }
25 | const request = {
26 | method: "POST",
27 | headers: { "Content-Type": "application/json" },
28 | body: JSON.stringify({
29 | firstName: firstName,
30 | email: email
31 | })
32 | }
33 | fetch(SERVER_URL + "user", request)
34 | .then(response => response.json())
35 | .then(response => {
36 | if (response.message) {
37 | window.flash("Ya este nombre de usuario y correo existen", "error")
38 | return
39 | }
40 |
41 | dispatch({ type: SET_USER_ID, userId: response._id, userName: response.firstName, userEmail: response.email })
42 | setRedirect(true)
43 | window.flash("Se ha registrado correctamente", "success")
44 | })
45 | .catch(error => window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error"))
46 |
47 |
48 | }
49 | if (redirect)
50 | return
51 |
52 | return (
53 |
54 |
55 |
57 | setName(event.target.value)} />
58 |
59 |
60 | setEmail(event.target.value)} />
61 | Ingrese Correo Electronico
62 |
63 |
64 |
65 | Cancelar
66 |
67 | Registrar
68 |
69 |
70 |
71 |
72 | )
73 | }
74 |
75 | export default Register
--------------------------------------------------------------------------------
/frontend/public/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/frontend/public/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | // Give the service worker access to Firebase Messaging.
2 | // Note that you can only use Firebase Messaging here. Other Firebase libraries
3 | // are not available in the service worker.
4 | importScripts('https://www.gstatic.com/firebasejs/8.4.2/firebase-app.js');
5 | importScripts('https://www.gstatic.com/firebasejs/8.4.2/firebase-messaging.js');
6 |
7 | // Initialize the Firebase app in the service worker by passing in
8 | // your app's Firebase config object.
9 | // https://firebase.google.com/docs/web/setup#config-object
10 | firebase.initializeApp({
11 | apiKey: "AIzaSyCOl76atKnpcjHsrK6MknUXRBekDK_dJl8",
12 | authDomain: "to-the-rescue-4c250.firebaseapp.com",
13 | projectId: "to-the-rescue-4c250",
14 | storageBucket: "to-the-rescue-4c250.appspot.com",
15 | messagingSenderId: "88808042476",
16 | appId: "1:88808042476:web:b3d36e2fff4b43c5c05ac9",
17 | measurementId: "G-SE04L5SX65"
18 | });
19 |
20 | // Retrieve an instance of Firebase Messaging so that it can handle background
21 | // messages.
22 | const messaging = firebase.messaging();
23 |
24 |
25 | messaging.onBackgroundMessage((payload) => {
26 | console.log('[firebase-messaging-sw.js] Received background message ', payload);
27 | // Customize notification here
28 | const notificationTitle = payload.notification.title;
29 | const notificationOptions = {
30 | body: payload.notification.body,
31 | // icon: '/firebase-logo.png'
32 | };
33 |
34 | self.registration.showNotification(notificationTitle,
35 | notificationOptions);
36 | });
37 |
38 | // ** CACHING **
39 |
40 | // Names of the two caches used in this version of the service worker.
41 | // Change to v2, etc. when you update any of the local resources, which will
42 | // in turn trigger the install event again.
43 | const PRECACHE = 'precache-v1';
44 | const RUNTIME = 'runtime';
45 |
46 | // A list of local resources we always want to be cached.
47 | const PRECACHE_URLS = [
48 | 'index.html',
49 | './', // Alias for index.html
50 | ];
51 |
52 | // The install handler takes care of precaching the resources we always need.
53 | self.addEventListener('install', event => {
54 | event.waitUntil(
55 | caches.open(PRECACHE)
56 | .then(cache => cache.addAll(PRECACHE_URLS))
57 | .then(self.skipWaiting())
58 | );
59 | });
60 |
61 | // The activate handler takes care of cleaning up old caches.
62 | self.addEventListener('activate', event => {
63 | const currentCaches = [PRECACHE, RUNTIME];
64 | event.waitUntil(
65 | caches.keys().then(cacheNames => {
66 | return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
67 | }).then(cachesToDelete => {
68 | return Promise.all(cachesToDelete.map(cacheToDelete => {
69 | return caches.delete(cacheToDelete);
70 | }));
71 | }).then(() => self.clients.claim())
72 | );
73 | });
74 |
75 | // The fetch handler serves responses for same-origin resources from a cache.
76 | // If no response is found, it populates the runtime cache with the response
77 | // from the network before returning it to the page.
78 | self.addEventListener('fetch', event => {
79 | // Skip cross-origin requests, like those for Google Analytics.
80 | if (event.request.url.startsWith(self.location.origin) || event.request.url.startsWith("https://drive.deta.sh/v1/")) {
81 | event.respondWith(
82 | caches.match(event.request).then(cachedResponse => {
83 | if (cachedResponse) {
84 | return cachedResponse;
85 | }
86 |
87 | return caches.open(RUNTIME).then(cache => {
88 | return fetch(event.request).then(response => {
89 | // Put a copy of the response in the runtime cache.
90 | return cache.put(event.request, response.clone()).then(() => {
91 | return response;
92 | });
93 | });
94 | });
95 | })
96 | );
97 | }
98 | });
--------------------------------------------------------------------------------
/frontend/src/Components/AboutUs/AboutUs.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react"
2 | import "./AboutUs.css"
3 | import TopHeader from "../TopHeader/TopHeader";
4 | import { GrInstagram } from "react-icons/gr"
5 | import { ImFacebook2 } from "react-icons/im"
6 | import { HiOutlineMail } from "react-icons/hi"
7 |
8 | function AboutUs() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | Bienvenido a Tribilin, sitio web para publicar sobre mascotas vulnerables en Cuba.
16 |
17 |
18 | Nuestra intención es concentrar toda la información dispersa en las redes
19 | sobre mascotas en adopción, abandonadas, perdidas o en estado crítico, para que sea más fácil
20 | encontrarles un hogar.
21 |
22 |
23 | Somos un quipo de tres, conformado por estudiantes y egresados de la Universidad de La Habana
24 | en las carreras de Diseño y Ciencias de la Computación.
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
¿Por qué el nombre Tribilin?
34 |
35 |
36 |
37 | Tribilin es un perrito callejero que hace 7 años fue adoptado por la familia de una de nuestras desarrolladoras.
38 | Es por animalitos como él que decidimos comenzar el proyecto, entonces qué mejor nombre que el suyo.
39 |
40 |
41 |
42 |
43 |
44 |
Cómo Colaborar
45 |
46 |
47 |
Tribilin es un proyecto open source. Tú también puedes colaborar! Visita nuestro{" "}
48 | repositorio en GitHub.
49 | Todo aporte es bienvenido!
50 |
51 |
52 |
53 |
54 |
55 |
Contáctanos
56 |
57 |
58 |
59 | Síguenos en nuestras redes sociales y cuéntanos qué crees de Tribilin.
60 | Tu opinión es importante para nosotros.
61 |
62 |
68 |
74 |
80 |
81 |
82 |
83 | )
84 | }
85 | export default AboutUs;
86 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import bodyParser from 'body-parser'
3 | import cors from 'cors'
4 | import { fileURLToPath } from 'url'
5 | import { dirname } from 'path'
6 |
7 | import MongooseConnection from './config/mongoose.js'
8 | import AlertManager from './managers/alert-manager.js'
9 | import UserManager from './managers/user-manager.js'
10 | import TokenManager from './managers/token-manager.js'
11 |
12 | const __dirname = dirname(fileURLToPath(import.meta.url));
13 |
14 | const app = express()
15 |
16 | app.use(cors({ origin: 'http://localhost:3000' }))
17 | app.use('/static', express.static(__dirname + "/static"))
18 |
19 | const connector = new MongooseConnection()
20 | connector.getConnection()
21 |
22 | while (connector.connection == null)
23 | continue
24 |
25 | export const connection = connector.connection
26 |
27 | const userManager = new UserManager()
28 | const alertManager = new AlertManager()
29 | const tokenManager = new TokenManager()
30 |
31 | app.use(bodyParser({ limit: '80mb' }))
32 | app.use(bodyParser.json());
33 | app.use(bodyParser.urlencoded({ extended: true }));
34 | app.use(bodyParser.text());
35 | app.use(bodyParser.json({ type: 'application/json' }));
36 |
37 |
38 | app.get("/", (req, res) => {
39 |
40 | res.send('BIENVENIDO A LA API DE AL RESCATE')
41 | })
42 |
43 | // * USERS
44 | app.get('/user/:id', async (req, res) => {
45 |
46 | var id = req.params.id
47 |
48 | const users = await userManager.find({ _id: id })
49 |
50 | const user = users.length > 0 ? users[0] : null
51 |
52 | res.json(user)
53 | })
54 |
55 | app.post('/user', async (req, res) => {
56 |
57 | const body = req.body
58 |
59 | const existingUser = await userManager.find(body)
60 | if (existingUser && existingUser.length)
61 | res.status(400).send({ message: "User already exists" })
62 |
63 | const user = await userManager.insert(body)
64 |
65 | if (body.token)
66 | await tokenManager.insert({
67 | token: body.token,
68 | user_id: user['_id']
69 | })
70 |
71 | res.json(user)
72 | })
73 |
74 | app.post('/user-login', async (req, res) => {
75 |
76 | const body = req.body
77 |
78 | const user = (await userManager.find({ email: body.email, firstName: body.firstName }))[0]
79 |
80 | res.json(user)
81 | })
82 |
83 | app.put('/user/:id', async (req, res) => {
84 |
85 | const body = req.body
86 | const id = req.params.id
87 |
88 | const user = (await userManager.update({ _id: id }, body))[0]
89 |
90 | res.json(user)
91 | })
92 |
93 | app.delete('/user/:id', async (req, res) => {
94 |
95 | const id = req.params.id
96 |
97 | const user = await userManager.delete({ _id: id })
98 |
99 | res.json(user)
100 | })
101 |
102 |
103 |
104 | // * ALERTS
105 | app.get('/alert/all', async (req, res) => {
106 |
107 | var alerts = await alertManager.find(req.query)
108 |
109 | res.json(alerts.sort((a, b) => b.date - a.date))
110 | })
111 |
112 | app.get('/alert/:id', async (req, res) => {
113 |
114 | var id = req.params.id
115 | var alert = (await alertManager.find({ _id: id }))[0]
116 |
117 | res.json(alert)
118 | })
119 |
120 | app.get('/alert-by-user/:id', async (req, res) => {
121 |
122 | const id = req.params.id
123 | const alerts = await alertManager.find({
124 | ...req.query,
125 | author_id: id
126 | })
127 |
128 | res.json(alerts.sort((a, b) => b.date - a.date))
129 | })
130 |
131 | app.post('/alert', async (req, res) => {
132 |
133 | var alert = req.body
134 |
135 | alert = await alertManager.insert(alert)
136 |
137 | res.json(alert)
138 | })
139 |
140 | app.put('/alert/:id', async (req, res) => {
141 |
142 | const body = req.body
143 | const id = req.params.id
144 |
145 | const user = (await alertManager.update({ _id: id }, body))[0]
146 |
147 | res.json(user)
148 | })
149 |
150 |
151 | app.delete('/alert/:id', async (req, res) => {
152 |
153 | const id = req.params.id
154 |
155 | const user = await alertManager.delete({ _id: id })
156 |
157 | res.json(user)
158 | })
159 |
160 | app.delete('/alert', async (req, res) => {
161 |
162 | const filter = req.body
163 |
164 | const user = await alertManager.delete(filter)
165 |
166 | res.json(user)
167 | })
168 |
169 | app.delete('/alert-expired', async (req, res) => {
170 |
171 | const { time } = req.body
172 | const now = Date.now()
173 |
174 | const alerts = await alertManager.find()
175 |
176 | let eraseList = []
177 |
178 | for (const alert of alerts){
179 | const timeDiff = (now - alert.date) / 1000
180 | if (timeDiff >= time)
181 | eraseList.push(alert._id)
182 | }
183 |
184 | const user = await alertManager.delete({ _id: {$in: eraseList}})
185 |
186 | res.json(user)
187 | })
188 |
189 | // * TOKENS
190 |
191 | app.post('/token', async (req, res) => {
192 |
193 | const body = req.body
194 |
195 | const token = await tokenManager.insert(body)
196 |
197 | res.json(token)
198 | })
199 |
200 | app.put('/token', async (req, res) => {
201 |
202 | const body = req.body
203 |
204 | const token = (await tokenManager.update({ token: body.token },
205 | { user_id: body.user_id }))
206 | res.json(token)
207 | })
208 |
209 | console.log('PORT:', process.env.PORT);
210 |
211 | app.listen(process.env.PORT || 8080)
212 |
213 | export default app
--------------------------------------------------------------------------------
/frontend/src/Components/Posts/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import { Link, Redirect } from "react-router-dom"
3 | import Post from "./Post/Post"
4 | import { connect } from "react-redux"
5 | import { POPULATE_POSTS } from "../../store/actions"
6 | import TopHeader from "../TopHeader/TopHeader"
7 | import "./Posts.css"
8 | import { SERVER_URL } from "../../Constants/constants"
9 | import Spinner from "../Layout/Spinner/Spinner"
10 | import PostsTabs from "../Layout/PostsTabs/PostsTabs"
11 | import { Button, Modal } from "react-bootstrap"
12 | import { FaFilter } from "react-icons/fa"
13 | import FilterForm from "../Layout/FilterForm/FilterForm"
14 | import Footer from "../Footer/Footer"
15 |
16 | class Posts extends Component {
17 | state = {
18 | loading: true,
19 | redirect: false,
20 | showFilterModal: false,
21 | activeKey: "all",
22 | search: "",
23 | }
24 |
25 | componentDidMount() {
26 | const search = new URLSearchParams(this.props.location.search)
27 | if (search.has("alert_type")) {
28 | this.setState({ activeKey: search.get("alert_type") })
29 | search.delete("alert_type")
30 | this.setState({ search: search.toString() })
31 | }
32 | else
33 | this.setState({ activeKey: "all" })
34 |
35 | fetch(SERVER_URL + "alert/all" + this.props.location.search)
36 | .then(response => response.json())
37 | .then(data => {
38 | this.props.populatePosts(data);
39 | this.setState({ loading: false })
40 | })
41 | .catch(error => {
42 | if (error.message === "Failed to fetch")
43 | window.flash("Sin conexión a Internet. Inténtelo de nuevo más tarde.", "error")
44 | else
45 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
46 |
47 | this.setState({ loading: false })
48 | })
49 | }
50 | componentDidUpdate(prevProps) {
51 | if (prevProps.location.search !== this.props.location.search) {
52 | const search = new URLSearchParams(this.props.location.search)
53 | if (!search.has("alert_type"))
54 | this.setState({ activeKey: "all" })
55 | else
56 | search.delete("alert_type")
57 | this.setState({ search: search.toString() })
58 |
59 | fetch(SERVER_URL + "alert/all" + this.props.location.search)
60 | .then(response => response.json())
61 | .then(data => {
62 | this.props.populatePosts(data);
63 | })
64 | .catch(error => {
65 | if (error.message === "Failed to fetch")
66 | window.flash("Sin conexión a Internet. Inténtelo de nuevo más tarde.", "error")
67 | else
68 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
69 |
70 | })
71 | }
72 | }
73 |
74 | render() {
75 | const posts = this.props.posts
76 | .map(post =>
88 | )
89 |
90 | const newPostHandler = (event) => {
91 | event.preventDefault()
92 | if (this.props.userId)
93 | this.setState({ redirect: true })
94 | else
95 | window.flash("Debe iniciar sesión para publicar una alerta", "error")
96 | }
97 | let body = posts !== [] ?
98 | posts :
99 | (
100 | No hay alertas publicadas recientemente.
101 |
)
102 | if (this.state.redirect)
103 | return
104 |
105 | if (this.state.loading)
106 | return
107 | return (
108 |
109 |
110 |
111 | { this.setState({ showFilterModal: true }) }}
114 | >
115 | Filtrar
116 |
117 |
118 |
123 |
newPostHandler(e)}
128 | >
129 |
130 |
131 |
132 |
133 |
{ this.setState({ showFilterModal: false }) }}>
134 |
135 | { this.setState({ showFilterModal: false }) }}
137 | />
138 |
139 |
140 |
141 |
142 | );
143 | }
144 | }
145 | const mapStateToProps = state => {
146 | return {
147 | posts: state.posts,
148 | userId: state.userId
149 | }
150 | }
151 | const mapDispatchToProps = dispatch => {
152 | return {
153 | populatePosts: (data) => dispatch({ type: POPULATE_POSTS, newPosts: data })
154 | }
155 | }
156 | export default connect(mapStateToProps, mapDispatchToProps)(Posts);
--------------------------------------------------------------------------------
/frontend/public/bootstrap-4.6.0-dist/css/bootstrap-reboot.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | font-family: sans-serif;
16 | line-height: 1.15;
17 | -webkit-text-size-adjust: 100%;
18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
19 | }
20 |
21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
22 | display: block;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
28 | font-size: 1rem;
29 | font-weight: 400;
30 | line-height: 1.5;
31 | color: #212529;
32 | text-align: left;
33 | background-color: #fff;
34 | }
35 |
36 | [tabindex="-1"]:focus:not(:focus-visible) {
37 | outline: 0 !important;
38 | }
39 |
40 | hr {
41 | box-sizing: content-box;
42 | height: 0;
43 | overflow: visible;
44 | }
45 |
46 | h1, h2, h3, h4, h5, h6 {
47 | margin-top: 0;
48 | margin-bottom: 0.5rem;
49 | }
50 |
51 | p {
52 | margin-top: 0;
53 | margin-bottom: 1rem;
54 | }
55 |
56 | abbr[title],
57 | abbr[data-original-title] {
58 | text-decoration: underline;
59 | -webkit-text-decoration: underline dotted;
60 | text-decoration: underline dotted;
61 | cursor: help;
62 | border-bottom: 0;
63 | -webkit-text-decoration-skip-ink: none;
64 | text-decoration-skip-ink: none;
65 | }
66 |
67 | address {
68 | margin-bottom: 1rem;
69 | font-style: normal;
70 | line-height: inherit;
71 | }
72 |
73 | ol,
74 | ul,
75 | dl {
76 | margin-top: 0;
77 | margin-bottom: 1rem;
78 | }
79 |
80 | ol ol,
81 | ul ul,
82 | ol ul,
83 | ul ol {
84 | margin-bottom: 0;
85 | }
86 |
87 | dt {
88 | font-weight: 700;
89 | }
90 |
91 | dd {
92 | margin-bottom: .5rem;
93 | margin-left: 0;
94 | }
95 |
96 | blockquote {
97 | margin: 0 0 1rem;
98 | }
99 |
100 | b,
101 | strong {
102 | font-weight: bolder;
103 | }
104 |
105 | small {
106 | font-size: 80%;
107 | }
108 |
109 | sub,
110 | sup {
111 | position: relative;
112 | font-size: 75%;
113 | line-height: 0;
114 | vertical-align: baseline;
115 | }
116 |
117 | sub {
118 | bottom: -.25em;
119 | }
120 |
121 | sup {
122 | top: -.5em;
123 | }
124 |
125 | a {
126 | color: #007bff;
127 | text-decoration: none;
128 | background-color: transparent;
129 | }
130 |
131 | a:hover {
132 | color: #0056b3;
133 | text-decoration: underline;
134 | }
135 |
136 | a:not([href]):not([class]) {
137 | color: inherit;
138 | text-decoration: none;
139 | }
140 |
141 | a:not([href]):not([class]):hover {
142 | color: inherit;
143 | text-decoration: none;
144 | }
145 |
146 | pre,
147 | code,
148 | kbd,
149 | samp {
150 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
151 | font-size: 1em;
152 | }
153 |
154 | pre {
155 | margin-top: 0;
156 | margin-bottom: 1rem;
157 | overflow: auto;
158 | -ms-overflow-style: scrollbar;
159 | }
160 |
161 | figure {
162 | margin: 0 0 1rem;
163 | }
164 |
165 | img {
166 | vertical-align: middle;
167 | border-style: none;
168 | }
169 |
170 | svg {
171 | overflow: hidden;
172 | vertical-align: middle;
173 | }
174 |
175 | table {
176 | border-collapse: collapse;
177 | }
178 |
179 | caption {
180 | padding-top: 0.75rem;
181 | padding-bottom: 0.75rem;
182 | color: #6c757d;
183 | text-align: left;
184 | caption-side: bottom;
185 | }
186 |
187 | th {
188 | text-align: inherit;
189 | text-align: -webkit-match-parent;
190 | }
191 |
192 | label {
193 | display: inline-block;
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | button {
198 | border-radius: 0;
199 | }
200 |
201 | button:focus:not(:focus-visible) {
202 | outline: 0;
203 | }
204 |
205 | input,
206 | button,
207 | select,
208 | optgroup,
209 | textarea {
210 | margin: 0;
211 | font-family: inherit;
212 | font-size: inherit;
213 | line-height: inherit;
214 | }
215 |
216 | button,
217 | input {
218 | overflow: visible;
219 | }
220 |
221 | button,
222 | select {
223 | text-transform: none;
224 | }
225 |
226 | [role="button"] {
227 | cursor: pointer;
228 | }
229 |
230 | select {
231 | word-wrap: normal;
232 | }
233 |
234 | button,
235 | [type="button"],
236 | [type="reset"],
237 | [type="submit"] {
238 | -webkit-appearance: button;
239 | }
240 |
241 | button:not(:disabled),
242 | [type="button"]:not(:disabled),
243 | [type="reset"]:not(:disabled),
244 | [type="submit"]:not(:disabled) {
245 | cursor: pointer;
246 | }
247 |
248 | button::-moz-focus-inner,
249 | [type="button"]::-moz-focus-inner,
250 | [type="reset"]::-moz-focus-inner,
251 | [type="submit"]::-moz-focus-inner {
252 | padding: 0;
253 | border-style: none;
254 | }
255 |
256 | input[type="radio"],
257 | input[type="checkbox"] {
258 | box-sizing: border-box;
259 | padding: 0;
260 | }
261 |
262 | textarea {
263 | overflow: auto;
264 | resize: vertical;
265 | }
266 |
267 | fieldset {
268 | min-width: 0;
269 | padding: 0;
270 | margin: 0;
271 | border: 0;
272 | }
273 |
274 | legend {
275 | display: block;
276 | width: 100%;
277 | max-width: 100%;
278 | padding: 0;
279 | margin-bottom: .5rem;
280 | font-size: 1.5rem;
281 | line-height: inherit;
282 | color: inherit;
283 | white-space: normal;
284 | }
285 |
286 | progress {
287 | vertical-align: baseline;
288 | }
289 |
290 | [type="number"]::-webkit-inner-spin-button,
291 | [type="number"]::-webkit-outer-spin-button {
292 | height: auto;
293 | }
294 |
295 | [type="search"] {
296 | outline-offset: -2px;
297 | -webkit-appearance: none;
298 | }
299 |
300 | [type="search"]::-webkit-search-decoration {
301 | -webkit-appearance: none;
302 | }
303 |
304 | ::-webkit-file-upload-button {
305 | font: inherit;
306 | -webkit-appearance: button;
307 | }
308 |
309 | output {
310 | display: inline-block;
311 | }
312 |
313 | summary {
314 | display: list-item;
315 | cursor: pointer;
316 | }
317 |
318 | template {
319 | display: none;
320 | }
321 |
322 | [hidden] {
323 | display: none !important;
324 | }
325 | /*# sourceMappingURL=bootstrap-reboot.css.map */
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributor guidelines
2 |
3 | We are excited about receiving all kinds of contributions from people around the world that want to help vulnerable animals in Cuba - just in Cuba so far :wink:.
4 | That's why we want to guide you through the process of contributing to this project so the time and effort of all contributors can really make a difference.
5 |
6 | In the next sections we'll be talking about the scope of contributions and the steps you need to follow so your contribution can reach the vulnerable animals
7 | out there.
8 |
9 | ## Scope of contributions
10 |
11 | What can you contribute with? Almost with anything related to this project. The following is a non-exhaustive list of areas in which you can contribute:
12 |
13 | * :books: Documentation
14 | * * For users
15 | * * For developers
16 | * :bug: Bug fixing and refactoring
17 | * :star: New features
18 | * :smiley: New projects (new mobile app for example :sweat_smile:)
19 |
20 | As you can see, a lot of work can be done!
21 |
22 | Of course, if you want to help in another way, like promotion for example, you can reach us in our social media in the [about us page](https://tribilin.netlify.app/about-us).
23 | This guide is for contributions associated with the development of the projects.
24 |
25 | So, what do you have to do to contribute?
26 |
27 | ## The contribution workflow
28 |
29 | It seems you are doing quite well because the first step for contributing is reading this guide! Now, let's talk about the following steps.
30 |
31 | ### Step 2: Read issues and pull-requests
32 |
33 | You maybe found a bug, or want to add some documentation but, what happen if there is another person working on that?
34 |
35 | We strongly recommend you to read the issues in the repo as well as the pending pull-requests so you can focus your energies on something new and don't waste your
36 | time.
37 |
38 | ### Step 3: Write an issue
39 |
40 | Once you are sure nobody is working in what you want to contribute with, it's time to let others know that you are working on it! This way, new contributors can avoid
41 | working in what you are doing. It is also a great place to discuss about ideas and recommendations. It can also happen that people who manage the projects won't
42 | agree with the kind of contribution you propose for several reasons. Make sure everybody is ok with what you will be doing before starting to work!
43 |
44 | Make sure **to label your issue appropriately**. We'll be talking about labeling in the **Step 5**.
45 |
46 | In case your contribution is originated from a report of others and that report was written in an issue, make sure to open a new issue so we can close the report issue
47 | with yours.
48 |
49 |
50 | ### Step 4: Fork this repo
51 |
52 | There is not too much to add. Just fork this repo cause you are going to work in your forked version.
53 |
54 | ### Step 5: Create a branch from dev
55 |
56 | After forking this repo and cloning it to your local environment, you need to checkout to the ```dev``` branch and create a new branch from there. We have a convention
57 | for branch naming:
58 |
59 | The name of your branch should follow the format: ```//```. Let's explain this further:
60 |
61 | * ```your identifier```: can be your name or any nickname you want to be identified with. E.g. ```jose```, or ```awesome-dev```
62 | * ```label```: we have multiple predefined labels you should use according with the kind of contribution you want to do:
63 | * * ```doc``` for documentation related contributions
64 | * * ```fix``` if you want to fix a bug
65 | * * ```feat``` if you want to add any new feature. E.g. adding maps, making a new mobile application, etc
66 | * * ```ref``` if you want to refactor some parts of the existing codebase
67 |
68 | The ```short description``` explains what you will be doing. It should be written in Pascal case.
69 |
70 | Examples of valid branch names are:
71 |
72 | * ```jose/doc/AddUserManual```
73 | * ```awesome-dev/feat/CreateMobileApp```
74 |
75 | #### Notes on commits
76 |
77 | We also have commit messages conventions. Your commit message should respect the following format:
78 |
79 | ```plain
80 | :
81 |
82 |
83 | ```
84 |
85 | For the label just apply what we've explained before. The ```!``` sign only has to be included if your changes are incompatible with the previous version of the app.
86 | Usually, if this happens, it is good that you add the optional body with a detailed explanation of what has been broken and how to fix it. The brief explanation should
87 | complete the sentence _If you apply this commit it will..._. The message header should be written in lowercase. Examples of valid commit messages are:
88 |
89 | * ```doc: add a user manual to documentation```
90 | * ```plain
91 | ref!: create some utility functions to reuse
92 |
93 | You need to import the getDBConnection function from utils in the backend whenever you need to connect to the DB
94 | ```
95 |
96 | ### Step 6: Make a pull request to the dev branch of the original repo
97 |
98 | After you have completed your work and **have made sure all the test passed after your changes (whenever it applies)**. You can proceed to make a pull-request to the
99 | **dev** branch of this repo. Please, write a detailed description of the changes you have made so it will be easier to make a review. Make sure you link the issue you
100 | published at the beginning of the process.
101 |
102 | The review process can take a while and we can have some nice debates about your changes. The result of such review could be:
103 |
104 | * **Change request:** we detected some things we want you to change or fix. You can do it and make a new pull request with the changes
105 | * **Not accepted:** we don't think your changes should be accepted. Sometimes it could be due to some violations of the rules stated above.
106 | * **Accepted!!!:** we think you have done it quite well and we'll merge your work in the repo!
107 |
108 | Please note that this is a work that is done in community. We watch for every member of this community to be respectful with others. We won't tolerate any offensive
109 | or violent comment! All the critics should be constructive and with good faith.
110 |
111 | ## Final notes
112 |
113 | We hope we haven't overwhelmed you with so many rules and conventions. We want everyone to feel encouraged to contribute to this project and to help vulnerable animals.
114 | They really need it!
115 |
116 | These rules are formulated with the spirit of organize and optimize the way everyone contributes. It is difficult sometimes to organize the work of so many people!
117 |
118 | So, what are you waiting for?
119 |
--------------------------------------------------------------------------------
/frontend/src/Components/Layout/FilterForm/FilterForm.js:
--------------------------------------------------------------------------------
1 | import { Form, Button, AccordionCollapse, AccordionToggle } from "react-bootstrap";
2 | import Accordion from "react-bootstrap/Accordion";
3 | import "./FilterForm.css"
4 | import { Redirect, useLocation } from "react-router";
5 | import { useState, useEffect } from "react";
6 | import { PROVINCES } from "../../../Constants/constants";
7 | import { RiArrowDownSLine } from "react-icons/ri";
8 | import { BsArrowClockwise } from "react-icons/bs";
9 |
10 | function FilterForm({ onHide }) {
11 | const location = useLocation()
12 | const activeFilters = new URLSearchParams(location.search)
13 |
14 | const [animal, setAnimal] = useState(activeFilters.get("animal"))
15 | const [gender, setGender] = useState(activeFilters.get("gender"))
16 | const [province, setProvince] = useState(activeFilters.getAll("province") || [])
17 | const [municipality, setMunicipality] = useState(activeFilters.get("municipality"))
18 | const [redirect, setRedirect] = useState(null)
19 |
20 | const checkElementById = (elementId) => {
21 | document.getElementById(elementId).checked = true
22 | }
23 | useEffect(() => {
24 |
25 | animal && checkElementById(`animal-${animal}`)
26 |
27 | gender && checkElementById(`gender-${gender}`)
28 |
29 | province.forEach(p => checkElementById(`province-${p}`))
30 |
31 | }, [])// eslint-disable-line react-hooks/exhaustive-deps
32 |
33 | const resetValues = () => {
34 | document.getElementById("filter-form").reset()
35 | setAnimal(null)
36 | setGender(null)
37 | setMunicipality(null)
38 | setProvince([])
39 | }
40 | const provinces = PROVINCES.map(p => {
41 | return {
49 | if (e.target.checked)
50 | setProvince([...province, e.target.value])
51 | else
52 | setProvince(province.filter(pr => pr !== e.target.value))
53 | }}
54 | />
55 | })
56 |
57 | const submitHandler = (event) => {
58 | event.preventDefault()
59 | onHide()
60 | let search = new URLSearchParams()
61 |
62 | animal && search.append("animal", animal)
63 | gender && search.append("gender", gender)
64 | municipality && search.append("municipality", municipality)
65 |
66 | province.forEach((p) => search.append("province", p))
67 | setRedirect("/alert/all/?" + search.toString())
68 | }
69 |
70 | if (redirect)
71 | return
72 |
73 | return (
74 |
83 |
84 |
Animal
85 |
86 |
setAnimal(e.target.value)}
93 | />
94 | setAnimal(e.target.value)}
101 | />
102 |
103 |
Sexo
104 |
105 |
setGender(e.target.value)}
112 | />
113 | setGender(e.target.value)}
120 | />
121 |
122 |
123 |
Municipio
124 |
125 | { setMunicipality(e.target.value) }}
128 | />
129 |
130 |
131 |
132 |
133 |
134 | Provincia
135 |
136 |
137 | {provinces}
138 |
139 |
140 |
141 |
142 |
143 |
148 | Cancelar
149 |
150 |
155 | Aplicar
156 |
157 |
158 |
159 |
160 |
161 | )
162 | }
163 | export default FilterForm
--------------------------------------------------------------------------------
/frontend/src/Components/NewPost/NewPost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import "./NewPost.css"
3 | import { Form, Button } from "react-bootstrap"
4 | import { SERVER_URL } from "../../Constants/constants"
5 | import { Link } from "react-router-dom"
6 | import { connect } from "react-redux"
7 | import imageCompression from 'browser-image-compression'
8 |
9 | class NewPost extends Component {
10 | state = {
11 | postForm: {
12 | author_id: this.props.userId,
13 | animal: "",
14 | gender: "Desconocido",
15 | age: "",
16 | picture_path: "",
17 | province: "",
18 | municipality: "",
19 | address: "",
20 | alert_type: "Perdido",
21 | email: "",
22 | phone: "",
23 | description: "",
24 | imgString: null,
25 |
26 | },
27 | imgUrl: "/default.png",
28 | validated: false,
29 | from: "home",
30 | }
31 | componentDidMount() {
32 | const from = this.props.match.params.from + this.props.location.search
33 | this.setState({ from: from })
34 | }
35 | submitHandler = (event) => {
36 | event.preventDefault();
37 | const form = event.target
38 | if (form.checkValidity() === false) {
39 | this.setState({ validated: true })
40 | return
41 | }
42 |
43 | const submitButton = document.getElementById("submit-post")
44 | submitButton.disabled = true
45 |
46 | const request = {
47 | method: "POST",
48 | headers: { "Content-Type": "application/json" },
49 | body: JSON.stringify(this.state.postForm)
50 | }
51 | fetch(SERVER_URL + "alert", request)
52 | .then(response => {
53 | window.flash("Alerta publicada con éxito", "success")
54 | this.props.history.push("/" + this.state.from)
55 | })
56 | .catch(error => {
57 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
58 | submitButton.disabled = false
59 | })
60 | }
61 |
62 | inputChangedHandler = (event, inputId) => {
63 | const updatedPostForm = { ...this.state.postForm }
64 | updatedPostForm[inputId] = event.target.value
65 |
66 | this.setState({ postForm: updatedPostForm })
67 | }
68 | changeImageHandler = (event) => {
69 | if (event.target.files[0]) {
70 | var imageFile = event.target.files[0];
71 |
72 | var options = {
73 | maxSizeMB: 1,
74 | maxWidthOrHeight: 1920,
75 | useWebWorker: true
76 | }
77 | imageCompression(imageFile, options)
78 | .then(compressedFile => {
79 | let reader = new FileReader()
80 |
81 | reader.readAsDataURL(compressedFile)
82 | reader.onload = () => {
83 | this.setState({
84 | imgUrl: URL.createObjectURL(compressedFile),
85 | postForm: { ...this.state.postForm, imgString: reader.result }
86 | });
87 | }
88 | })
89 | .catch(() =>
90 | window.flash("Ha ocurrido un error al cargar la imagen", "error")
91 | );
92 | }
93 | }
94 | render() {
95 | return (
96 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
113 |
114 |
115 |
116 |
117 | { this.inputChangedHandler(event, "alert_type") }} >
118 | Condición del animal
119 | Perdido
120 | Abandonado
121 | Adopción
122 | Crítico
123 |
124 |
125 | Seleccione un elemento de la lista
126 |
127 |
128 |
129 |
130 | { this.inputChangedHandler(event, "animal") }}>
131 | Animal
132 | Perro
133 | Gato
134 |
135 |
136 | Seleccione un elemento de la lista
137 |
138 |
139 |
140 |
141 | { this.inputChangedHandler(event, "gender") }}>
142 | Sexo
143 | Hembra
144 | Macho
145 | Desconocido
146 |
147 |
148 |
149 |
150 | { this.inputChangedHandler(event, "age") }} />
151 |
152 | {/* ############################### */}
153 |
154 | { this.inputChangedHandler(event, "province") }}>
155 | Provincia
156 | Pinar del río
157 | Artemisa
158 | La Habana
159 | Mayabeque
160 | Matanzas
161 | Cienfuegos
162 | Villa Clara
163 | Sancti Spíritus
164 | Ciego de Ávila
165 | Camagüey
166 | Las Tunas
167 | Granma
168 | Holguín
169 | Santiago de Cuba
170 | Guantánamo
171 | Isla de la Juventud
172 |
173 |
174 | Seleccione un elemento de la lista
175 |
176 |
177 |
178 | { this.inputChangedHandler(event, "municipality") }} />
179 |
180 |
181 | { this.inputChangedHandler(event, "address") }} />
182 |
183 |
184 |
185 | { this.inputChangedHandler(event, "phone") }} />
186 |
187 |
188 | { this.inputChangedHandler(event, "email") }} />
189 |
190 |
191 | { this.inputChangedHandler(event, "description") }} />
192 |
193 |
194 |
195 | Publicar
196 |
197 |
198 |
199 |
200 | )
201 | }
202 | }
203 | const mapStateToProps = (state) => {
204 | return {
205 | userId: state.userId
206 | }
207 | }
208 | export default connect(mapStateToProps)(NewPost)
--------------------------------------------------------------------------------
/frontend/src/Components/EditPost/EditPost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import { Form, Button } from "react-bootstrap"
3 | import { Link } from "react-router-dom"
4 | import { connect } from "react-redux"
5 | import { SERVER_URL } from "../../Constants/constants"
6 | import { POPULATE_SELECTED_POST } from "../../store/actions"
7 |
8 | class EditPost extends Component {
9 | state = {
10 | postForm: {
11 | author_id: this.props.post.author_id,
12 | animal: this.props.post.animal,
13 | gender: this.props.post.gender,
14 | age: this.props.post.age,
15 | picture_path: this.props.post.picture_path,
16 | province: this.props.post.province,
17 | municipality: this.props.post.municipality,
18 | address: this.props.post.address,
19 | alert_type: this.props.post.alert_type,
20 | email: this.props.post.email,
21 | phone: this.props.post.phone,
22 | description: this.props.post.description,
23 | imgString: undefined,
24 |
25 | },
26 | imgUrl: "/default.png",
27 | validated: false,
28 | from: "home",
29 | }
30 | componentDidMount() {
31 | const from = this.props.match.params.from
32 | this.setState({ from: from })
33 | }
34 | submitHandler = (event) => {
35 | event.preventDefault();
36 | const form = event.target
37 | if (form.checkValidity() === false) {
38 | this.setState({ validated: true })
39 | return
40 | }
41 |
42 | const request = {
43 | method: "PUT",
44 | headers: { "Content-Type": "application/json" },
45 | body: JSON.stringify(this.state.postForm)
46 | }
47 | fetch(SERVER_URL + "alert/" + this.props.post._id, request)
48 | .then(response => {
49 | console.log(response)
50 | this.props.history.push(`/post-details/${this.props.post._id}/${this.state.from}`)
51 | this.props.populatePosts(this.state.postForm)
52 | })
53 | .catch(error => {
54 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
55 | })
56 |
57 | }
58 | inputChangedHandler = (event, inputId) => {
59 | const updatedPostForm = { ...this.state.postForm }
60 | updatedPostForm[inputId] = event.target.value
61 |
62 | this.setState({ postForm: updatedPostForm })
63 | }
64 | changeImageHandler = (event) => {
65 | if (event.target.files[0]) {
66 | let reader = new FileReader()
67 | reader.readAsDataURL(event.target.files[0])
68 | reader.onload = () => {
69 | this.setState({
70 | imgUrl: URL.createObjectURL(event.target.files[0]),
71 | postForm: {
72 | ...this.state.postForm,
73 | imgString: reader.result
74 | }
75 | });
76 | }
77 | }
78 | }
79 | render() {
80 | return (
81 |
83 | {/*
84 |
85 |
86 |
87 |
88 |
89 |
94 |
95 |
96 |
97 |
98 |
*/}
99 |
100 |
101 | { this.inputChangedHandler(event, "alert_type") }} value={this.state.postForm.alert_type}>
102 | Condición del animal
103 | Perdido
104 | Abandonado
105 | Adopción
106 | Crítico
107 |
108 |
109 | Seleccione un elemento de la lista
110 |
111 |
112 |
113 |
114 | { this.inputChangedHandler(event, "animal") }} value={this.state.postForm.animal}>
115 | Animal
116 | Perro
117 | Gato
118 |
119 |
120 | Seleccione un elemento de la lista
121 |
122 |
123 |
124 |
125 | { this.inputChangedHandler(event, "gender") }} value={this.state.postForm.gender}>
126 | Sexo
127 | Hembra
128 | Macho
129 | Desconocido
130 |
131 |
132 |
133 |
134 | { this.inputChangedHandler(event, "age") }} value={this.state.postForm.age} />
135 |
136 |
137 | { this.inputChangedHandler(event, "province") }} value={this.state.postForm.province}>
138 | Pinar del río
139 | Artemisa
140 | La Habana
141 | Mayabeque
142 | Matanzas
143 | Cienfuegos
144 | Villa Clara
145 | Sancti Spíritus
146 | Ciego de Ávila
147 | Camagüey
148 | Las Tunas
149 | Granma
150 | Holguín
151 | Santiago de Cuba
152 | Guantánamo
153 | Isla de la Juventud
154 |
155 |
156 | Seleccione un elemento de la lista
157 |
158 |
159 |
160 | { this.inputChangedHandler(event, "municipality") }} value={this.state.postForm.municipality} />
161 |
162 |
163 | { this.inputChangedHandler(event, "address") }} value={this.state.postForm.address} />
164 |
165 |
166 |
167 |
168 | { this.inputChangedHandler(event, "phone") }} value={this.state.postForm.phone} />
169 |
170 |
171 | { this.inputChangedHandler(event, "email") }} value={this.state.postForm.email} />
172 |
173 |
174 | { this.inputChangedHandler(event, "description") }} value={this.state.postForm.description} />
175 |
176 |
177 |
178 |
179 | Cancelar
180 |
181 |
182 | Guardar
183 |
184 |
185 |
186 |
187 |
188 | )
189 | }
190 | }
191 | const mapStateToProps = state => {
192 | return {
193 | post: state.selectedPost
194 | }
195 | }
196 | const mapDispatchToProps = dispatch => {
197 | return {
198 | populatePosts: (data) => dispatch({ type: POPULATE_SELECTED_POST, newPost: data })
199 | }
200 | }
201 |
202 | export default connect(mapStateToProps, mapDispatchToProps)(EditPost)
--------------------------------------------------------------------------------
/frontend/src/Components/PostDetails/PostDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { connect } from "react-redux"
3 | import { POPULATE_SELECTED_POST } from "../../store/actions"
4 | import { Badge, Card, Modal, Button, Spinner } from 'react-bootstrap'
5 | import { Link, Redirect } from 'react-router-dom'
6 | import { SERVER_URL, TOY_DETA_KEY, DETA_URL, TOY_DETA_ID } from '../../Constants/constants'
7 | import CustomSpinner from '../Layout/Spinner/Spinner'
8 | import "./PostDetails.css"
9 | import { MdEdit, MdDelete, MdMessage } from "react-icons/md"
10 | import { ImWhatsapp } from "react-icons/im"
11 | import { IoCall } from 'react-icons/io5'
12 | import { DETA_PROJECT_ID } from '../../Constants/constants'
13 | import { DETA_API_KEY } from '../../Constants/constants'
14 | import PictureModal from '../Layout/PictureModal/PictureModal'
15 |
16 | class PostDetails extends Component {
17 | state = {
18 | loading: true,
19 | redirect: false,
20 | show: false,
21 | editRedirect: false,
22 | from: "home",
23 | imgUrl: null,
24 | showImg: false
25 | }
26 |
27 | componentDidMount() {
28 | const from = this.props.match.params.from + this.props.location.search
29 | fetch(SERVER_URL + "alert/" + this.props.match.params.id)
30 | .then(response => response.json())
31 | .then(data => {
32 | let picture_path = data.picture_path
33 | this.props.populateSelectedPost(data)
34 | this.setState({ loading: false, from: from })
35 |
36 | const projectId = DETA_PROJECT_ID || TOY_DETA_ID
37 | const detaDriveName = 'photos'
38 | const urlSuffix = `files/download?name=${picture_path}`
39 |
40 | const imgUrl = picture_path === "" ?
41 | "/default.png" :
42 | `${DETA_URL}${projectId}/${detaDriveName}/${urlSuffix}`
43 |
44 | if (imgUrl !== "/default.png")
45 | fetch(imgUrl, { headers: { "X-Api-Key": DETA_API_KEY || TOY_DETA_KEY, "Cache-Control": "public, max-age=604800, immutable" } })
46 | .then(response => response.text())
47 | .then(text => {
48 | const pict = "data:image/png;base64," + text
49 | this.setState({ imgUrl: pict })
50 | })
51 | else
52 | this.setState({ imgUrl: imgUrl })
53 | })
54 | .catch(error => {
55 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
56 | })
57 |
58 | }
59 |
60 |
61 | render() {
62 | const deleteHandler = () => {
63 | const postId = this.props.post._id
64 | fetch(SERVER_URL + "alert/" + postId, { method: "DELETE" })
65 | .then(response => response.json())
66 | .then(data => {
67 | this.setState({ redirect: true })
68 | window.flash("Alerta borrada con éxito", "success")
69 | })
70 | .catch(error => {
71 | window.flash("Ha ocurrido un error. Inténtelo de nuevo más tarde.", "error")
72 | })
73 | }
74 |
75 | const showPictureHandler = () => {
76 | if (this.state.imgUrl !== "/default.png")
77 | this.setState({ showImg: true })
78 | }
79 | const post = { ...this.props.post }
80 | const date = new Date(`${post.date}`)
81 | const linkStatus = {
82 | "Perdido": "perdido ",
83 | "Adopción": "en adopción ",
84 | "Crítico": "en estado crítico ",
85 | "Abandonado": "abandonado "
86 | }
87 | const linkAnimal = {
88 | "Perro": "Perrito ",
89 | "Gato": "Gatico "
90 | }
91 | const linkText = linkAnimal[post.animal] + linkStatus[post.alert_type] + "en el municipio " + post.municipality + ". Toca el link para más detalles \n"
92 |
93 |
94 |
95 | if (this.state.redirect)
96 | return
97 | if (this.state.editRedirect)
98 | return
99 |
100 | if (this.state.loading)
101 | return
102 |
103 | return (
104 |
105 |
106 |
110 |
111 |
112 |
113 |
114 |
{post.alert_type}
115 |
116 | {
117 | this.state.imgUrl !== null ?
118 |
:
126 |
127 | }
128 |
129 |
130 |
{post.municipality ? `${post.municipality},` : null} {post.province}
131 |
132 |
133 |
134 |
135 | {post.description &&
136 |
137 |
138 | {post.description}
139 |
140 |
141 | }
142 |
143 | Animal : {post.animal}
144 |
145 | {post.gender &&
146 |
147 | Sexo : {post.gender}
148 |
149 | }
150 | {post.age &&
151 |
152 | Edad : {post.age}
153 |
154 | }
155 | {post.address &&
156 |
157 | Dirección : {post.address}
158 |
159 | }
160 |
161 | {post.email}
162 |
163 | {post.phone &&
164 |
165 |
Teléfono :{post.phone}
166 |
170 |
171 |
172 | }
173 |
174 | Publicado el día: {date.toLocaleDateString("es-ES")}
175 |
176 |
198 |
199 |
200 |
201 | {this.props.userId !== null && post.author_id === this.props.userId ?
202 |
203 |
this.setState({ editRedirect: true })}>
204 |
205 |
206 |
207 |
this.setState({ show: true })} >
208 |
209 |
210 |
:
211 | null
212 | }
213 | this.setState({ show: false })} centered>
214 |
215 |
216 |
217 | ¿Está seguro que desea borrar esta alerta?
218 |
219 |
220 | Borrar
221 | this.setState({ show: false })}>Cancelar
222 |
223 |
224 |
225 |
226 | this.setState({ showImg: false })}
229 | imgSrc={this.state.imgUrl}
230 | />
231 |
232 | )
233 | }
234 | }
235 |
236 | const mapStateToProps = state => {
237 | return {
238 | post: state.selectedPost,
239 | userId: state.userId
240 | }
241 | }
242 |
243 | const mapDispatchToProps = dispatch => {
244 | return {
245 | populateSelectedPost: (data) => dispatch({ type: POPULATE_SELECTED_POST, newPost: data })
246 | }
247 | }
248 |
249 | export default connect(mapStateToProps, mapDispatchToProps)(PostDetails)
--------------------------------------------------------------------------------
/frontend/public/js/popper.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) Federico Zivolo 2020
3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
5 | //# sourceMappingURL=popper.min.js.map
6 |
--------------------------------------------------------------------------------
/push-notification-test/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@firebase/analytics-types@0.4.0":
6 | version "0.4.0"
7 | resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.4.0.tgz#d6716f9fa36a6e340bc0ecfe68af325aa6f60508"
8 | integrity sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==
9 |
10 | "@firebase/analytics@0.6.9":
11 | version "0.6.9"
12 | resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.6.9.tgz#1f6d015e27beb6e0be2363908803b3c57d98f1da"
13 | integrity sha512-G0PkfMq/4tpDXwk/S2LKrXUWiz5tpQ6o2Lf6esgdEcDLpimPl32TrioNkDEDz8Xp0mzpY04UKwvYjT5xuzoKug==
14 | dependencies:
15 | "@firebase/analytics-types" "0.4.0"
16 | "@firebase/component" "0.4.1"
17 | "@firebase/installations" "0.4.25"
18 | "@firebase/logger" "0.2.6"
19 | "@firebase/util" "1.0.0"
20 | tslib "^2.1.0"
21 |
22 | "@firebase/app-types@0.6.2":
23 | version "0.6.2"
24 | resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.2.tgz#8578cb1061a83ced4570188be9e225d54e0f27fb"
25 | integrity sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==
26 |
27 | "@firebase/app@0.6.20":
28 | version "0.6.20"
29 | resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.20.tgz#07d3bdda6fbe34bac34bcba7f2f92ce394a29753"
30 | integrity sha512-5zstJ3Cxw9H5cxfdaAhCH7WHVaRLPhCcgVNwKp6dWeTx2QkIdNvHainX8Vr2RaZchw4MxRjkPfwNVOaq2oFStQ==
31 | dependencies:
32 | "@firebase/app-types" "0.6.2"
33 | "@firebase/component" "0.4.1"
34 | "@firebase/logger" "0.2.6"
35 | "@firebase/util" "1.0.0"
36 | dom-storage "2.1.0"
37 | tslib "^2.1.0"
38 | xmlhttprequest "1.8.0"
39 |
40 | "@firebase/auth-interop-types@0.1.6":
41 | version "0.1.6"
42 | resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964"
43 | integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==
44 |
45 | "@firebase/auth-types@0.10.3":
46 | version "0.10.3"
47 | resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.3.tgz#2be7dd93959c8f5304c63e09e98718e103464d8c"
48 | integrity sha512-zExrThRqyqGUbXOFrH/sowuh2rRtfKHp9SBVY2vOqKWdCX1Ztn682n9WLtlUDsiYVIbBcwautYWk2HyCGFv0OA==
49 |
50 | "@firebase/auth@0.16.5":
51 | version "0.16.5"
52 | resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.16.5.tgz#703a9f1208e14fa0801798bd926c4a8f59fc97c4"
53 | integrity sha512-Cgs/TlVot2QkbJyEphvKmu+2qxYlNN+Q2+29aqZwryrnn1eLwlC7nT89K6O91/744HJRtiThm02bMj2Wh61E3Q==
54 | dependencies:
55 | "@firebase/auth-types" "0.10.3"
56 |
57 | "@firebase/component@0.4.1":
58 | version "0.4.1"
59 | resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.4.1.tgz#c8269f21149a4c81e385531428ad4c086a8f47db"
60 | integrity sha512-f0IbIsoe33QzOj554rmDL04PyeZX/nNZYOAwlTzKmHq/JoFN6YoySi+0ZLyCtFrnRgw6zNnR/POXKOdfljWqZA==
61 | dependencies:
62 | "@firebase/util" "1.0.0"
63 | tslib "^2.1.0"
64 |
65 | "@firebase/database-types@0.7.2":
66 | version "0.7.2"
67 | resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.7.2.tgz#449c4b36ec59a1ad9089797b540e2ba1c0d4fcbf"
68 | integrity sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg==
69 | dependencies:
70 | "@firebase/app-types" "0.6.2"
71 |
72 | "@firebase/database@0.9.11":
73 | version "0.9.11"
74 | resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.9.11.tgz#87fba58cb6f3e93d56a88b9130507c9988fa2f97"
75 | integrity sha512-f1+39Q1Zxnnu7sswz5oOZUC3CTMEEQR6hFxmE+ZJiw5+/Uy/wK11DS/KofSj0VCe0cPhPHxNzmLjUU5/5yu9Kw==
76 | dependencies:
77 | "@firebase/auth-interop-types" "0.1.6"
78 | "@firebase/component" "0.4.1"
79 | "@firebase/database-types" "0.7.2"
80 | "@firebase/logger" "0.2.6"
81 | "@firebase/util" "1.0.0"
82 | faye-websocket "0.11.3"
83 | tslib "^2.1.0"
84 |
85 | "@firebase/firestore-types@2.2.0":
86 | version "2.2.0"
87 | resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-2.2.0.tgz#9a3f3f2906232c3b4a726d988a6ef077f35f9093"
88 | integrity sha512-5kZZtQ32FIRJP1029dw+ZVNRCclKOErHv1+Xn0pw/5Fq3dxroA/ZyFHqDu+uV52AyWHhNLjCqX43ibm4YqOzRw==
89 |
90 | "@firebase/firestore@2.2.5":
91 | version "2.2.5"
92 | resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-2.2.5.tgz#a7fdc3edcf4c165f827bec3b80115099462d6482"
93 | integrity sha512-Ucg3cy79u4KPlPs5//c5Af92OrZJigSUem2JxWKHlGSgjl71CR6Pa9WMkv6ot5qNJcxwx4FdDtqrtIpKA/xPDw==
94 | dependencies:
95 | "@firebase/component" "0.4.1"
96 | "@firebase/firestore-types" "2.2.0"
97 | "@firebase/logger" "0.2.6"
98 | "@firebase/util" "1.0.0"
99 | "@firebase/webchannel-wrapper" "0.4.1"
100 | "@grpc/grpc-js" "^1.0.0"
101 | "@grpc/proto-loader" "^0.5.0"
102 | node-fetch "2.6.1"
103 | tslib "^2.1.0"
104 |
105 | "@firebase/functions-types@0.4.0":
106 | version "0.4.0"
107 | resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.4.0.tgz#0b789f4fe9a9c0b987606c4da10139345b40f6b9"
108 | integrity sha512-3KElyO3887HNxtxNF1ytGFrNmqD+hheqjwmT3sI09FaDCuaxGbOnsXAXH2eQ049XRXw9YQpHMgYws/aUNgXVyQ==
109 |
110 | "@firebase/functions@0.6.7":
111 | version "0.6.7"
112 | resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.6.7.tgz#3a5be76edeb990b8bb80c0e68755baa2f46362c8"
113 | integrity sha512-IDw2ww28Tj8t947ySVO9wHghlwNl4bIUo5tPUzAbipfgLlj3GeHwqhvSv++O/ILBu4Rk7KD7cbxtw/rziATHNA==
114 | dependencies:
115 | "@firebase/component" "0.4.1"
116 | "@firebase/functions-types" "0.4.0"
117 | "@firebase/messaging-types" "0.5.0"
118 | node-fetch "2.6.1"
119 | tslib "^2.1.0"
120 |
121 | "@firebase/installations-types@0.3.4":
122 | version "0.3.4"
123 | resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.4.tgz#589a941d713f4f64bf9f4feb7f463505bab1afa2"
124 | integrity sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==
125 |
126 | "@firebase/installations@0.4.25":
127 | version "0.4.25"
128 | resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.25.tgz#532a50418afc01b3cbc8fdab55e3b168cec66193"
129 | integrity sha512-szQ2bpI5NHTRuZAqXNZLq7bkZ1iTURPmojj7xWjBRxyMnDd6lLQ/Ht8Wut0ESH7uzbFNqmZ9oBMh2U9fpBIniA==
130 | dependencies:
131 | "@firebase/component" "0.4.1"
132 | "@firebase/installations-types" "0.3.4"
133 | "@firebase/util" "1.0.0"
134 | idb "3.0.2"
135 | tslib "^2.1.0"
136 |
137 | "@firebase/logger@0.2.6":
138 | version "0.2.6"
139 | resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.2.6.tgz#3aa2ca4fe10327cabf7808bd3994e88db26d7989"
140 | integrity sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==
141 |
142 | "@firebase/messaging-types@0.5.0":
143 | version "0.5.0"
144 | resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.5.0.tgz#c5d0ef309ced1758fda93ef3ac70a786de2e73c4"
145 | integrity sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==
146 |
147 | "@firebase/messaging@0.7.9":
148 | version "0.7.9"
149 | resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.7.9.tgz#b97acca8900eba6a04d8510638a6986172436675"
150 | integrity sha512-zzEmtpBdauT0n0JA5eN/dHeQZkQj/bbfl7CNmhA0EpKU2wTRFZCJYAOZkZEw8OD9/D/aDRcEk3Qq+5I1XcugZA==
151 | dependencies:
152 | "@firebase/component" "0.4.1"
153 | "@firebase/installations" "0.4.25"
154 | "@firebase/messaging-types" "0.5.0"
155 | "@firebase/util" "1.0.0"
156 | idb "3.0.2"
157 | tslib "^2.1.0"
158 |
159 | "@firebase/performance-types@0.0.13":
160 | version "0.0.13"
161 | resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6"
162 | integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==
163 |
164 | "@firebase/performance@0.4.11":
165 | version "0.4.11"
166 | resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.4.11.tgz#928132583219a15bb64049e76f8d7c5a5045e7d6"
167 | integrity sha512-SQb9QpAkgpPS1QnRLxNAXFTCrW/VT9MidVcJVHuBrCCW9sYY+QVuuWYpaGR4zQDsTx2e/UGUXJgw+z0vaQ0Q6w==
168 | dependencies:
169 | "@firebase/component" "0.4.1"
170 | "@firebase/installations" "0.4.25"
171 | "@firebase/logger" "0.2.6"
172 | "@firebase/performance-types" "0.0.13"
173 | "@firebase/util" "1.0.0"
174 | tslib "^2.1.0"
175 |
176 | "@firebase/polyfill@0.3.36":
177 | version "0.3.36"
178 | resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.36.tgz#c057cce6748170f36966b555749472b25efdb145"
179 | integrity sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==
180 | dependencies:
181 | core-js "3.6.5"
182 | promise-polyfill "8.1.3"
183 | whatwg-fetch "2.0.4"
184 |
185 | "@firebase/remote-config-types@0.1.9":
186 | version "0.1.9"
187 | resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz#fe6bbe4d08f3b6e92fce30e4b7a9f4d6a96d6965"
188 | integrity sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==
189 |
190 | "@firebase/remote-config@0.1.36":
191 | version "0.1.36"
192 | resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.36.tgz#02a2f5799d22728ac30a28273cbd299c82fda33a"
193 | integrity sha512-aQXaBDkEzFix3ycjPiP+4OPSXZmUbFunOiVi20XS9kRZrZfNhCH3HdBYwL1Nl9/AvcOnlZfX+lqa2LuHVXmuwA==
194 | dependencies:
195 | "@firebase/component" "0.4.1"
196 | "@firebase/installations" "0.4.25"
197 | "@firebase/logger" "0.2.6"
198 | "@firebase/remote-config-types" "0.1.9"
199 | "@firebase/util" "1.0.0"
200 | tslib "^2.1.0"
201 |
202 | "@firebase/storage-types@0.4.1":
203 | version "0.4.1"
204 | resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.4.1.tgz#da6582ae217e3db485c90075dc71100ca5064cc6"
205 | integrity sha512-IM4cRzAnQ6QZoaxVZ5MatBzqXVcp47hOlE28jd9xXw1M9V7gfjhmW0PALGFQx58tPVmuUwIKyoEbHZjV4qRJwQ==
206 |
207 | "@firebase/storage@0.5.1":
208 | version "0.5.1"
209 | resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.5.1.tgz#a05be43e8ad9be2304bf5cd6d1a13e938db9f157"
210 | integrity sha512-cDlq2ibKlQo1RVRKeUtzpnvbEAKebxg/Yd5OTJGoPGwoWLeZ6FZhhRP/dI2ZBj2BetkqTdvlDGtxamOkMbHeeQ==
211 | dependencies:
212 | "@firebase/component" "0.4.1"
213 | "@firebase/storage-types" "0.4.1"
214 | "@firebase/util" "1.0.0"
215 | tslib "^2.1.0"
216 |
217 | "@firebase/util@1.0.0":
218 | version "1.0.0"
219 | resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.0.0.tgz#cbe8ec610a84a7d2fc804af31010305941f4a34b"
220 | integrity sha512-KIEyuyrYKKtit+lAl66c2GVvooM1Pb+Yw/9yuSga1HKYMxNZwSsIMXU8X97sLZf7WJaanV1XNJEMkZTw3xKEoA==
221 | dependencies:
222 | tslib "^2.1.0"
223 |
224 | "@firebase/webchannel-wrapper@0.4.1":
225 | version "0.4.1"
226 | resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.1.tgz#600f2275ff54739ad5ac0102f1467b8963cd5f71"
227 | integrity sha512-0yPjzuzGMkW1GkrC8yWsiN7vt1OzkMIi9HgxRmKREZl2wnNPOKo/yScTjXf/O57HM8dltqxPF6jlNLFVtc2qdw==
228 |
229 | "@grpc/grpc-js@^1.0.0":
230 | version "1.3.0"
231 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.0.tgz#c6febaaf00ce7f53b5ec52c7cf3e2a4725b82bcb"
232 | integrity sha512-fiL7ZaGg2HBiFtmv6m34d5jEgEtNXfctjzB3f7b3iuT7olBX4mHLMOqOBmGTTSOTfNRQJH5+vsyk6mEz3I0Q7Q==
233 | dependencies:
234 | "@types/node" ">=12.12.47"
235 |
236 | "@grpc/proto-loader@^0.5.0":
237 | version "0.5.6"
238 | resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.6.tgz#1dea4b8a6412b05e2d58514d507137b63a52a98d"
239 | integrity sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==
240 | dependencies:
241 | lodash.camelcase "^4.3.0"
242 | protobufjs "^6.8.6"
243 |
244 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
245 | version "1.1.2"
246 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
247 | integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
248 |
249 | "@protobufjs/base64@^1.1.2":
250 | version "1.1.2"
251 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
252 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
253 |
254 | "@protobufjs/codegen@^2.0.4":
255 | version "2.0.4"
256 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
257 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
258 |
259 | "@protobufjs/eventemitter@^1.1.0":
260 | version "1.1.0"
261 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
262 | integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
263 |
264 | "@protobufjs/fetch@^1.1.0":
265 | version "1.1.0"
266 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
267 | integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
268 | dependencies:
269 | "@protobufjs/aspromise" "^1.1.1"
270 | "@protobufjs/inquire" "^1.1.0"
271 |
272 | "@protobufjs/float@^1.0.2":
273 | version "1.0.2"
274 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
275 | integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
276 |
277 | "@protobufjs/inquire@^1.1.0":
278 | version "1.1.0"
279 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
280 | integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
281 |
282 | "@protobufjs/path@^1.1.2":
283 | version "1.1.2"
284 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
285 | integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
286 |
287 | "@protobufjs/pool@^1.1.0":
288 | version "1.1.0"
289 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
290 | integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
291 |
292 | "@protobufjs/utf8@^1.1.0":
293 | version "1.1.0"
294 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
295 | integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
296 |
297 | "@types/long@^4.0.1":
298 | version "4.0.1"
299 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
300 | integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
301 |
302 | "@types/node@>=12.12.47", "@types/node@>=13.7.0":
303 | version "15.0.1"
304 | resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.1.tgz#ef34dea0881028d11398be5bf4e856743e3dc35a"
305 | integrity sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==
306 |
307 | core-js@3.6.5:
308 | version "3.6.5"
309 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
310 | integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
311 |
312 | dom-storage@2.1.0:
313 | version "2.1.0"
314 | resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39"
315 | integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==
316 |
317 | faye-websocket@0.11.3:
318 | version "0.11.3"
319 | resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
320 | integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
321 | dependencies:
322 | websocket-driver ">=0.5.1"
323 |
324 | firebase@^8.4.2:
325 | version "8.4.2"
326 | resolved "https://registry.yarnpkg.com/firebase/-/firebase-8.4.2.tgz#5b3eff63683bd7379677b79f8efaf8e2ed2be5d4"
327 | integrity sha512-PUzobCZ2z6nEvb3IRxz9L1H/q7e7LaKhx6vEUYhKTIK7WMYg+iiUE67IY4Lvh5/IWugclAH8SX5ny8azSy2hTw==
328 | dependencies:
329 | "@firebase/analytics" "0.6.9"
330 | "@firebase/app" "0.6.20"
331 | "@firebase/app-types" "0.6.2"
332 | "@firebase/auth" "0.16.5"
333 | "@firebase/database" "0.9.11"
334 | "@firebase/firestore" "2.2.5"
335 | "@firebase/functions" "0.6.7"
336 | "@firebase/installations" "0.4.25"
337 | "@firebase/messaging" "0.7.9"
338 | "@firebase/performance" "0.4.11"
339 | "@firebase/polyfill" "0.3.36"
340 | "@firebase/remote-config" "0.1.36"
341 | "@firebase/storage" "0.5.1"
342 | "@firebase/util" "1.0.0"
343 |
344 | http-parser-js@>=0.5.1:
345 | version "0.5.3"
346 | resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
347 | integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==
348 |
349 | idb@3.0.2:
350 | version "3.0.2"
351 | resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384"
352 | integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==
353 |
354 | lodash.camelcase@^4.3.0:
355 | version "4.3.0"
356 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
357 | integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
358 |
359 | long@^4.0.0:
360 | version "4.0.0"
361 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
362 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
363 |
364 | node-fetch@2.6.1:
365 | version "2.6.1"
366 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
367 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
368 |
369 | promise-polyfill@8.1.3:
370 | version "8.1.3"
371 | resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
372 | integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==
373 |
374 | protobufjs@^6.8.6:
375 | version "6.11.1"
376 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.1.tgz#327dcee492fbcd421a6cd6200f79bfed6efb2405"
377 | integrity sha512-7KCLPvNGF/oDv/OXz+Zn2ViJx8x9WUKs5HrxZ5Gz0F2LD8jSTcsA1z9tBtOlPVVnJELOaFL+zj+oLc/WWSqlzw==
378 | dependencies:
379 | "@protobufjs/aspromise" "^1.1.2"
380 | "@protobufjs/base64" "^1.1.2"
381 | "@protobufjs/codegen" "^2.0.4"
382 | "@protobufjs/eventemitter" "^1.1.0"
383 | "@protobufjs/fetch" "^1.1.0"
384 | "@protobufjs/float" "^1.0.2"
385 | "@protobufjs/inquire" "^1.1.0"
386 | "@protobufjs/path" "^1.1.2"
387 | "@protobufjs/pool" "^1.1.0"
388 | "@protobufjs/utf8" "^1.1.0"
389 | "@types/long" "^4.0.1"
390 | "@types/node" ">=13.7.0"
391 | long "^4.0.0"
392 |
393 | safe-buffer@>=5.1.0:
394 | version "5.2.1"
395 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
396 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
397 |
398 | tslib@^2.1.0:
399 | version "2.2.0"
400 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
401 | integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
402 |
403 | websocket-driver@>=0.5.1:
404 | version "0.7.4"
405 | resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
406 | integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
407 | dependencies:
408 | http-parser-js ">=0.5.1"
409 | safe-buffer ">=5.1.0"
410 | websocket-extensions ">=0.1.1"
411 |
412 | websocket-extensions@>=0.1.1:
413 | version "0.1.4"
414 | resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
415 | integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
416 |
417 | whatwg-fetch@2.0.4:
418 | version "2.0.4"
419 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
420 | integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
421 |
422 | xmlhttprequest@1.8.0:
423 | version "1.8.0"
424 | resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
425 | integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
426 |
--------------------------------------------------------------------------------