diff --git a/.circleci/config.yml b/.circleci/config.yml index 76476381..71ebc7be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: build: docker: # specify the version you desire here - - image: circleci/ruby:2.6.2-node-browsers + - image: circleci/ruby:2.6.3-node-browsers environment: BUNDLER_VERSION: 2.0.1 RAILS_ENV: test diff --git a/.env.sample b/.env.sample index 2b4467a5..e49c6a51 100644 --- a/.env.sample +++ b/.env.sample @@ -2,9 +2,9 @@ AWS_BUCKET= AWS_REGION= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= -POSTGRES_USER: postgres -POSTGRES_PASSWORD: postgres -POSTGRES_PORT: 5434 +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5434 FACEBOOK_APP_ID= FACEBOOK_APP_SECRET= CALLBACK_URL=http://localhost:3000/users/auth/facebook/callback diff --git a/app/controllers/adoption_controller.rb b/app/controllers/adoption_controller.rb index 864b463c..d1ec7b1f 100644 --- a/app/controllers/adoption_controller.rb +++ b/app/controllers/adoption_controller.rb @@ -1,6 +1,6 @@ class AdoptionController < UserAreaController def index - @pets = Pet.actived + @pets = Pet.active end def show diff --git a/app/controllers/ngos_controller.rb b/app/controllers/ngos_controller.rb index 4026e621..0d06e33e 100644 --- a/app/controllers/ngos_controller.rb +++ b/app/controllers/ngos_controller.rb @@ -1,6 +1,6 @@ class NgosController < UserAreaController def index - @ngos = Ngo.actived + @ngos = Ngo.active end def show diff --git a/app/controllers/v1/ngos_controller.rb b/app/controllers/v1/ngos_controller.rb index 31cd5d44..b5e76593 100644 --- a/app/controllers/v1/ngos_controller.rb +++ b/app/controllers/v1/ngos_controller.rb @@ -7,36 +7,15 @@ def index def show render json: { - ngo: Ngo.last + ngo: Ngo.last }.to_json end -end - -# TODO: extract to a class -# TODO: add specs -class ListNgos - def all - ngos = Ngo.actived - add_extra_fields(ngos) - end - private - - def add_extra_fields(ngos) - ngos.map do |ngo| - ngo.attributes.merge( - logo_path: logo_path(ngo), - fantasy_name_url: ngo.fantasy_name_url - ) - end - end - - # TODO: this should have it's own class and maybe returned by the model - def logo_path(ngo) - if ngo.image.attached? - Rails.application.routes.url_helpers.rails_blob_path(ngo.image, only_path: true) - else - ActionController::Base.helpers.image_path('image_not_found.png') - end + def cities + # TODO: test + render json: { + cities: ListNgos.new.all.map { |ngo| ngo['city'] && { id: ngo['city'], name: ngo['city'] } }.uniq.compact + }.to_json end end + diff --git a/app/controllers/v1/pets_for_adoption_controller.rb b/app/controllers/v1/pets_for_adoption_controller.rb new file mode 100644 index 00000000..0dd1b7f3 --- /dev/null +++ b/app/controllers/v1/pets_for_adoption_controller.rb @@ -0,0 +1,29 @@ +class V1::PetsForAdoptionController < ApplicationController + skip_before_action :verify_authenticity_token + + def index + render json: { + pets: ListPets.new.all(Pet.active, params[:user_email]) + }.to_json + end + + def register_interest + response = { success: "Finished with success" } + status = :ok + + begin + ::RegisterAdoptionInterest.new.save!(register_interest_params[:user_email], register_interest_params[:pet_id]) + rescue ActiveRecord::RecordNotFound + response = { error: "User not found" } + status = :not_found + end + + render json: response.to_json, status: status + end + + private + + def register_interest_params + params.require(:register_interest).permit(:user_email, :pet_id) + end +end diff --git a/app/javascript/components/App.js b/app/javascript/components/App.js index 8478ca4e..6fdd0534 100644 --- a/app/javascript/components/App.js +++ b/app/javascript/components/App.js @@ -6,11 +6,18 @@ import configureStore from '../configureStore'; import NgosList from "../containers/NgosList"; import NgoPage from "../containers/NgoPage"; import ErrorBoundary from "./ErrorBoundary"; -import AdoptionList from "../containers/AdoptionList"; +import AdoptionList from "../containers/AdoptionList/AdoptionList"; + +require('typeface-roboto'); const store = configureStore(); class App extends React.Component { + constructor(props) { + super(props); + this.state = { userEmail: this.props.userEmail }; + } + render() { return ( @@ -21,7 +28,7 @@ class App extends React.Component { }/> }/> - }/> + }/> diff --git a/app/javascript/components/Backdrop/Backdrop.jsx b/app/javascript/components/Backdrop/Backdrop.jsx new file mode 100644 index 00000000..2ae33e5f --- /dev/null +++ b/app/javascript/components/Backdrop/Backdrop.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import styles from './Backdrop.sass'; + +const backdrop = (props) => ( + props.show ?
: null +); + +export default backdrop; diff --git a/app/javascript/components/Backdrop/Backdrop.sass b/app/javascript/components/Backdrop/Backdrop.sass new file mode 100644 index 00000000..cb3173fc --- /dev/null +++ b/app/javascript/components/Backdrop/Backdrop.sass @@ -0,0 +1,8 @@ +.Backdrop + width: 100% + height: 100% + position: fixed + z-index: 100 + left: 0 + top: 0 + background-color: rgba(0, 0, 0, 0.5) diff --git a/app/javascript/components/SelectInput/SelectInput.jsx b/app/javascript/components/SelectInput/SelectInput.jsx new file mode 100644 index 00000000..97330262 --- /dev/null +++ b/app/javascript/components/SelectInput/SelectInput.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './SelectInput.sass'; + +const SelectInput = (props) => { + return ( +
+ + +
+ ); +}; + +export default SelectInput; diff --git a/app/javascript/components/SelectInput/SelectInput.sass b/app/javascript/components/SelectInput/SelectInput.sass new file mode 100644 index 00000000..f2313390 --- /dev/null +++ b/app/javascript/components/SelectInput/SelectInput.sass @@ -0,0 +1,29 @@ +.SelectInput + border: 0 + border-radius: 4px + margin-bottom: 10px + width: 200px + height: 50px + background-color: #ebebeb + font-size: 0 + padding: 6px + + *:focus + outline: none + +.SelectInput label + font-size: 10px + font-family: Roboto, sans-serif + color: #9b9b9b + padding: 2px 8px 0 + margin-bottom: 0 + +.SelectInput select + color: #232628 + font-size: 14px + font-family: Roboto, sans-serif + border: 0 + width: 100% + background: none + -webkit-appearance: none + padding-left: 8px diff --git a/app/javascript/components/SimpleCircularButton/SimpleCircularButton.jsx b/app/javascript/components/SimpleCircularButton/SimpleCircularButton.jsx new file mode 100644 index 00000000..97a0256e --- /dev/null +++ b/app/javascript/components/SimpleCircularButton/SimpleCircularButton.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './SimpleCircularButton.sass'; + +const SimpleCircularButton = (props) => { + return ( + + ); +}; + +export default SimpleCircularButton; diff --git a/app/javascript/components/SimpleCircularButton/SimpleCircularButton.sass b/app/javascript/components/SimpleCircularButton/SimpleCircularButton.sass new file mode 100644 index 00000000..d289180e --- /dev/null +++ b/app/javascript/components/SimpleCircularButton/SimpleCircularButton.sass @@ -0,0 +1,18 @@ +.SimpleCircularButton + width: 240px + height: 50px + border-radius: 29px + background-color: #83be7d + font-family: Roboto, sans-serif + font-size: 16px + font-weight: normal + font-style: normal + font-stretch: normal + line-height: normal + letter-spacing: normal + color: #ffffff + text-transform: uppercase + border: 0 + +.SimpleCircularButton:hover + background-color: #68af60 diff --git a/app/javascript/components/SimpleHeaderText/SimpleHeaderText.sass b/app/javascript/components/SimpleHeaderText/SimpleHeaderText.sass index 9c44c705..f7427286 100644 --- a/app/javascript/components/SimpleHeaderText/SimpleHeaderText.sass +++ b/app/javascript/components/SimpleHeaderText/SimpleHeaderText.sass @@ -2,8 +2,8 @@ .title width: 448px height: 49px - font-family: museo-sans, sans-serif - font-size: 36px + font-family: Roboto, sans-serif + font-size: 32px font-weight: bold font-style: normal font-stretch: normal @@ -14,8 +14,8 @@ .subtitle width: 740px height: 26px - font-family: museo-sans, sans-serif - font-size: 19px + font-family: Roboto, sans-serif + font-size: 16px font-weight: 500 font-style: normal font-stretch: normal diff --git a/app/javascript/components/SimpleModal/SimpleModal.jsx b/app/javascript/components/SimpleModal/SimpleModal.jsx new file mode 100644 index 00000000..27c6145b --- /dev/null +++ b/app/javascript/components/SimpleModal/SimpleModal.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import styles from './SimpleModal.sass'; +import Backdrop from '../Backdrop/Backdrop'; + +class SimpleModal extends React.Component { + shouldComponentUpdate(nextProps) { + return nextProps.show !== this.props.show; + } + + componentWillUpdate() { + console.log('[SimpleModal] WillUpdate') + } + + render() { + return ( +
+ +
+ {this.props.children} +
+
+ ) + } +} + +export default SimpleModal; diff --git a/app/javascript/components/SimpleModal/SimpleModal.sass b/app/javascript/components/SimpleModal/SimpleModal.sass new file mode 100644 index 00000000..bec84218 --- /dev/null +++ b/app/javascript/components/SimpleModal/SimpleModal.sass @@ -0,0 +1,17 @@ +.SimpleModal + position: fixed + z-index: 500 + background-color: white + width: 70% + border: 1px solid #ccc + box-shadow: 1px 1px 1px black + padding: 16px + left: 15% + top: 30% + box-sizing: border-box + transition: all 0.3s ease-out + +@media (min-width: 600px) + .SimpleModal + width: 500px + left: calc(50% - 250px) diff --git a/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.jsx b/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.jsx new file mode 100644 index 00000000..583d5bee --- /dev/null +++ b/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './SimpleSubmitButton.sass'; + +const SimpleSubmitButton = (props) => { + return ( + + ); +}; + +export default SimpleSubmitButton; diff --git a/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.sass b/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.sass new file mode 100644 index 00000000..cbf01355 --- /dev/null +++ b/app/javascript/components/SimpleSubmitButton/SimpleSubmitButton.sass @@ -0,0 +1,18 @@ +.SimpleSubmitButton + width: 200px + height: 50px + border-radius: 4px + background-color: #ffa0a0 + font-family: Roboto, sans-serif + font-size: 16px + font-weight: normal + font-style: normal + font-stretch: normal + line-height: normal + letter-spacing: normal + color: #ffffff + text-transform: uppercase + border: 0 + +.SimpleSubmitButton:hover + background-color: #ff8a8a diff --git a/app/javascript/components/TextInput/TextInput.jsx b/app/javascript/components/TextInput/TextInput.jsx new file mode 100644 index 00000000..b1872049 --- /dev/null +++ b/app/javascript/components/TextInput/TextInput.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import styles from './TextInput.sass'; + +const TextInput = (props) => { + return ( +
+ +
+ ); +}; + +export default TextInput; diff --git a/app/javascript/components/TextInput/TextInput.sass b/app/javascript/components/TextInput/TextInput.sass new file mode 100644 index 00000000..371d7245 --- /dev/null +++ b/app/javascript/components/TextInput/TextInput.sass @@ -0,0 +1,17 @@ +.TextInput + border: 0 + border-radius: 4px + margin-bottom: 10px + width: 300px + height: 50px + background-color: #ebebeb + padding: 12px 6px + +.TextInput input + color: #232628 + font-size: 14px + font-family: Roboto, sans-serif + border: 0 + width: 100% + background: none + padding-left: 8px diff --git a/app/javascript/configureStore.js b/app/javascript/configureStore.js index e35505ac..ef4b89bd 100644 --- a/app/javascript/configureStore.js +++ b/app/javascript/configureStore.js @@ -15,6 +15,10 @@ function rootReducer(state, action) { return { ngos: action.json.ngos }; case "GET_NGO_SUCCESS": return { ngo: action.json.ngo }; + case "GET_ADOPTION_SUCCESS": + return { pets: action.json.pets }; + case "GET_NGO_CITIES_SUCCESS": + return { cities: action.json.cities }; default: return state; } diff --git a/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.jsx b/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.jsx new file mode 100644 index 00000000..4296a4d3 --- /dev/null +++ b/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './AdoptionButton.sass'; + +const AdoptionButton = (props) => { + return ( + + ); +}; + +export default AdoptionButton; diff --git a/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.sass b/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.sass new file mode 100644 index 00000000..5baf0d01 --- /dev/null +++ b/app/javascript/containers/AdoptionCard/AdoptionButton/AdoptionButton.sass @@ -0,0 +1,16 @@ +.AdoptionButton + text-transform: uppercase + border: 0 + font-family: Roboto, sans-serif + font-style: normal + font-weight: 500 + font-size: 15px + line-height: 10px + color: #FFFFFF + width: 85px + height: 35px + background: #8FBC81 + border-radius: 22px + +.AdoptionButton:hover + background-color: #7DA570 diff --git a/app/javascript/containers/AdoptionCard/AdoptionCard.jsx b/app/javascript/containers/AdoptionCard/AdoptionCard.jsx new file mode 100644 index 00000000..8ecb6b91 --- /dev/null +++ b/app/javascript/containers/AdoptionCard/AdoptionCard.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import styles from './AdoptionCard.sass' +import {Link} from "react-router-dom"; +import TimeAgo from 'javascript-time-ago' +// Load pt-BR locale for time ago +import br from 'javascript-time-ago/locale/pt' +import AdoptionButton from "./AdoptionButton/AdoptionButton"; + +TimeAgo.addLocale(br); + +const AdoptionCard = (props) => { + var moment = require('moment'); + const postedAtDateTime = moment(props.postedAt).toDate(); + const timeAgo = new TimeAgo('pt-BR'); + + function ageLabelText(age) { + if (age == null) { + return "0 anos"; + } else if (age === 1) { + return `${age} ano`; + } else { + return `${age} anos`; + } + } + + function getNgoDescription(ngo) { + const city = ngo.city ? ngo.city : ''; + const state = ngo.state ? ngo.state : ''; + + if (city === '' && state === '') { + // TODO: should be required on ngos form + return ngo.fantasy_name; + } + + return ngo.fantasy_name + ", " + city + "-" + state; + } + + function getPetDetail(sex, age) { + const sexDescription = sex === "f" ? "Fêmea" : "Macho"; + + return sexDescription + ", " + ageLabelText(age) + } + + return ( +
+
+ Imagem do pet +
+ +
+ {/*{ TODO: implement behaviour of button when user is not logged in }*/} +
+ +
+
+
+ ); +}; + +export default AdoptionCard; diff --git a/app/javascript/containers/AdoptionCard/AdoptionCard.sass b/app/javascript/containers/AdoptionCard/AdoptionCard.sass new file mode 100644 index 00000000..f615dc6d --- /dev/null +++ b/app/javascript/containers/AdoptionCard/AdoptionCard.sass @@ -0,0 +1,116 @@ +.AdoptionCard + height: 486px + -webkit-box-flex: 0 + display: block + margin: 60px 25px + width: 300px + min-width: 300px + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24), 0 0 2px rgba(0, 0, 0, 0.12) + border-radius: 3px + background: #FAFAFA + + .pictureBox + width: 100% + height: 240px + background-color: #1c2d3f + + .picture + width: 100% + height: 100% + + .cardContent + margin: 10px + display: flex + flex-direction: column + + .city + height: 19px + font-family: Roboto, sans-serif + font-size: 12px + color: rgba(0, 0, 0, 0.543846) !important + margin: 1px 0 10px + + .petName + height: 32px + font-family: Roboto, sans-serif + font-size: 18px + font-weight: 500 + font-style: normal + font-stretch: normal + line-height: 1.6 + letter-spacing: normal + color: #1d1d1d !important + + .petDescription + height: 106px + font-family: Roboto, sans-serif + font-size: 14px + color: rgba(0, 0, 0, 0.5438) !important + font-family: Roboto, sans-serif + font-style: normal + font-weight: normal + line-height: 17px + margin-top: 10px + text-align: justify + + .postDetail + display: flex + justify-content: space-between + height: 14px + + .ngoDetail + display: flex + justify-content: space-between + margin: auto 0 + + .ngoPicture + width: 14px + height: 14px + border-radius: 100% + margin-right: 4px !important + margin: auto + + .ngoName + font-family: Roboto, sans-serif + font-size: 12px + font-style: normal + font-stretch: normal + letter-spacing: normal + color: #9B9B9B !important + font-weight: normal + line-height: 20px + + .postTime + font-family: Roboto, sans-serif + font-size: 12px + font-style: normal + font-stretch: normal + letter-spacing: normal + color: #9B9B9B !important + font-weight: normal + line-height: 20px + height: 14px + text-align: center + + .smallText + font-family: Roboto, sans-serif + font-size: 12px + font-style: normal + font-stretch: normal + letter-spacing: normal + color: #9B9B9B !important + font-weight: normal + line-height: 20px + + .adoptionCardBottom + display: table-cell + width: 500px + height: 50px + vertical-align: middle + text-align: center + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24), 0 0 2px rgba(0, 0, 0, 0.12) + border-radius: 3px + background-color: #F2F3F3 + + @media only screen and (max-width: 600px) + width: 500px !important diff --git a/app/javascript/containers/AdoptionList.jsx b/app/javascript/containers/AdoptionList.jsx deleted file mode 100644 index b00646af..00000000 --- a/app/javascript/containers/AdoptionList.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react" -import SimpleHeaderText from "../components/SimpleHeaderText/SimpleHeaderText"; - -class AdoptionList extends React.Component { - render() { - return ( -
- -
- ); - } -} - -export default AdoptionList; diff --git a/app/javascript/containers/AdoptionList/AdoptionList.jsx b/app/javascript/containers/AdoptionList/AdoptionList.jsx new file mode 100644 index 00000000..a69c1991 --- /dev/null +++ b/app/javascript/containers/AdoptionList/AdoptionList.jsx @@ -0,0 +1,126 @@ +import React from "react" +import SimpleHeaderText from "../../components/SimpleHeaderText/SimpleHeaderText"; +import styles from './AdoptionList.sass' +import AdoptionCard from "../AdoptionCard/AdoptionCard"; +import {createStructuredSelector} from "reselect"; +import {connect} from "react-redux"; +import AdoptionFilterBox from "./FilterBox/AdoptionFilterBox"; +import SimpleModal from "../../components/SimpleModal/SimpleModal"; + +const GET_ADOPTION_REQUEST = 'GET_ADOPTION_REQUEST'; +const GET_ADOPTION_SUCCESS = 'GET_ADOPTION_SUCCESS'; + +function fetchPetsForAdoption() { + return dispatch => { + dispatch({type: GET_ADOPTION_REQUEST}); + return fetch(`../v1/pets_for_adoption.json`) + .then(response => response.json()) + .then(json => dispatch(fetchPetsForAdoptionSuccess(json))) + .catch(error => console.log(error)); + } +} + +export function fetchPetsForAdoptionSuccess(json) { + return { + type: GET_ADOPTION_SUCCESS, + json + }; +} + +class AdoptionList extends React.Component { + state = { + showAdoptingModal: false, + }; + + componentWillMount() { + const {fetchPetsForAdoption} = this.props; + fetchPetsForAdoption(); + } + + addAdoptionInterestHandler = (userEmail, petId) => { + if (userEmail) { + let body = JSON.stringify({register_interest: {user_email: userEmail, pet_id: petId}}); + + fetch(`../v1/pets_for_adoption/register_interest`, { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: body, + }) + .then(response => console.log(response.json())) + .catch(function (error) { + // TODO: handle error + console.log(error); + }); + } + + this.setState({showAdoptingModal: true}); + }; + + adoptingCancelHandler = () => { + this.setState({showAdoptingModal: false}); + }; + + petList = (pets) => { + return pets.map(pet => { + return this.addAdoptionInterestHandler(this.props.userEmail, pet.id)} + />; + }) + }; + + render() { + const {pets, userEmail} = this.props; + + return ( +
+ + {userEmail ? +
+

Você está a uma pata mais próxima de adotar o seu animalzinho. A ONG está sabendo do seu interesse e entrará em contato quando tiver mais informações.

+
+ : +
+

Você precisa estar logado para adotar.

+ Ir para tela de login +
+ } +
+ {/**/} + {/**/} +
+ {pets && this.petList(pets)} + {/* Trick to align last row of cards with flexbox */} +