diff --git a/Gemfile b/Gemfile index acb306d..bc5482a 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,7 @@ gem 'jbuilder', '~> 2.7' gem 'active_model_serializers', '~> 0.10.10' gem 'bcrypt', '~> 3.1.13' gem 'rack-attack', '~> 6.2', '>= 6.2.2' +gem 'rack-cors' gem 'will_paginate', '~> 3.3' # Use Active Storage variant diff --git a/Gemfile.lock b/Gemfile.lock index af92ad9..b76c2fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,6 +134,8 @@ GEM rack (2.2.2) rack-attack (6.2.2) rack (>= 1.0, < 3) + rack-cors (1.1.1) + rack (>= 2.0.0) rack-proxy (0.6.5) rack rack-test (1.1.0) @@ -276,6 +278,7 @@ DEPENDENCIES pry-rails puma (~> 4.1) rack-attack (~> 6.2, >= 6.2.2) + rack-cors rails (~> 6.0.2, >= 6.0.2.2) rspec-rails (~> 4.0) rubocop diff --git a/app/controllers/api/volunteers_controller.rb b/app/controllers/api/volunteers_controller.rb index b58b432..19a9e12 100644 --- a/app/controllers/api/volunteers_controller.rb +++ b/app/controllers/api/volunteers_controller.rb @@ -17,7 +17,7 @@ def auth volunteer = Volunteer.find_by(email: params[:auth][:email]) if volunteer&.authenticate(params[:auth][:password]) session[:volunteer_id] = volunteer.id - render json: volunteer, status: :ok + render json: volunteer, status: :created else session[:volunteer_id] = nil render json: { message: 'Authentication failed', code: 401 }, status: :unauthorized diff --git a/app/javascript/client/components/Forms/Controls/Input/Input.js b/app/javascript/client/components/Forms/Controls/Input/Input.js new file mode 100644 index 0000000..a1ee8a4 --- /dev/null +++ b/app/javascript/client/components/Forms/Controls/Input/Input.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Input = (props) => { + return ( +
+ +
+ +
+
+ ); +}; + +Input.propTypes = { + label: PropTypes.string, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + type: PropTypes.string, + placeholder: PropTypes.string, + customInputClass: PropTypes.string, + onChange: PropTypes.func.isRequired, +}; + +Input.defaultProps = { + label: '', + type: 'string', + placeholder: '', + customInputClass: 'input', +}; + +export default Input; diff --git a/app/javascript/client/components/Forms/Controls/Input/index.js b/app/javascript/client/components/Forms/Controls/Input/index.js new file mode 100644 index 0000000..a122bd4 --- /dev/null +++ b/app/javascript/client/components/Forms/Controls/Input/index.js @@ -0,0 +1,3 @@ +import Input from './Input'; + +export default Input; diff --git a/app/javascript/client/components/Forms/Controls/index.js b/app/javascript/client/components/Forms/Controls/index.js new file mode 100644 index 0000000..1e902d1 --- /dev/null +++ b/app/javascript/client/components/Forms/Controls/index.js @@ -0,0 +1,3 @@ +import Input from './Input'; + +export { Input }; diff --git a/app/javascript/client/components/Forms/HelpOfferForm/HelpOfferForm.js b/app/javascript/client/components/Forms/HelpOfferForm/HelpOfferForm.js new file mode 100644 index 0000000..3d8ce92 --- /dev/null +++ b/app/javascript/client/components/Forms/HelpOfferForm/HelpOfferForm.js @@ -0,0 +1,10 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const HelpOfferForm = (props) => { + return
; +}; + +HelpOfferForm.propTypes = {}; + +export default HelpOfferForm; diff --git a/app/javascript/client/components/Forms/HelpOfferForm/index.js b/app/javascript/client/components/Forms/HelpOfferForm/index.js new file mode 100644 index 0000000..a870051 --- /dev/null +++ b/app/javascript/client/components/Forms/HelpOfferForm/index.js @@ -0,0 +1,3 @@ +import HelpOfferForm from './HelpOfferForm'; + +export default HelpOfferForm; diff --git a/app/javascript/client/components/Forms/VolunteerLoginForm/VolunteerLoginForm.js b/app/javascript/client/components/Forms/VolunteerLoginForm/VolunteerLoginForm.js new file mode 100644 index 0000000..6e5f233 --- /dev/null +++ b/app/javascript/client/components/Forms/VolunteerLoginForm/VolunteerLoginForm.js @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Input } from '../Controls'; + +const VolunteerLoginForm = (props) => { + const [volunteerFormData, setVolunteerFormData] = useState({ + email: '', + password: '', + }); + + const handleInputChange = (e) => { + setVolunteerFormData({ + ...volunteerFormData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + console.log(volunteerFormData); + }; + + return ( +
+

Iniciar sessão

+
+ + + + +
+
+ +
+
+
+
+ ); +}; + +VolunteerLoginForm.propTypes = {}; + +export default VolunteerLoginForm; diff --git a/app/javascript/client/components/Forms/VolunteerLoginForm/index.js b/app/javascript/client/components/Forms/VolunteerLoginForm/index.js new file mode 100644 index 0000000..ef878ae --- /dev/null +++ b/app/javascript/client/components/Forms/VolunteerLoginForm/index.js @@ -0,0 +1,3 @@ +import VolunteerLoginForm from './VolunteerLoginForm'; + +export default VolunteerLoginForm; diff --git a/app/javascript/client/components/Forms/VolunteerRegisterForm/VolunteerRegisterForm.js b/app/javascript/client/components/Forms/VolunteerRegisterForm/VolunteerRegisterForm.js new file mode 100644 index 0000000..4acac57 --- /dev/null +++ b/app/javascript/client/components/Forms/VolunteerRegisterForm/VolunteerRegisterForm.js @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Input } from '../Controls'; + +const VolunteerRegisterForm = (props) => { + const [volunteerFormData, setVolunteerFormData] = useState({ + fullname: '', + cellphone: '', + email: '', + password: '', + province: '', + county: '', + types_of_availability: '', + }); + + const handleInputChange = (e) => { + setVolunteerFormData({ + ...volunteerFormData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + props.registerNewVolunteer(volunteerFormData); + }; + + return ( +
+

Criar nova conta

+
+ + + + + + + + + + + + +
+
+ +
+
+
+
+ ); +}; + +VolunteerRegisterForm.propTypes = { + registerNewVolunteer: PropTypes.func.isRequired, +}; + +export default VolunteerRegisterForm; diff --git a/app/javascript/client/components/Forms/VolunteerRegisterForm/index.js b/app/javascript/client/components/Forms/VolunteerRegisterForm/index.js new file mode 100644 index 0000000..6273175 --- /dev/null +++ b/app/javascript/client/components/Forms/VolunteerRegisterForm/index.js @@ -0,0 +1,3 @@ +import VolunteerRegisterForm from './VolunteerRegisterForm'; + +export default VolunteerRegisterForm; diff --git a/app/javascript/client/components/Forms/index.js b/app/javascript/client/components/Forms/index.js new file mode 100644 index 0000000..75d2e19 --- /dev/null +++ b/app/javascript/client/components/Forms/index.js @@ -0,0 +1,5 @@ +import HelpOfferForm from './HelpOfferForm'; +import VolunteerLoginForm from './VolunteerLoginForm'; +import VolunteerRegisterForm from './VolunteerRegisterForm'; + +export { HelpOfferForm, VolunteerLoginForm, VolunteerRegisterForm }; diff --git a/app/javascript/client/components/HelpPage/HelpPage.js b/app/javascript/client/components/HelpPage/HelpPage.js index 57a6539..80dcda8 100644 --- a/app/javascript/client/components/HelpPage/HelpPage.js +++ b/app/javascript/client/components/HelpPage/HelpPage.js @@ -1,12 +1,76 @@ -import React, { useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { useParams } from 'react-router-dom'; import PropTypes from 'prop-types'; +import { + VolunteerRegisterForm, + VolunteerLoginForm, + HelpOfferForm, +} from '../Forms'; + import { loadHelp } from '../../store/helps/actions'; +import { registerVolunteer } from '../../store/volunteers/actions'; const HelpPage = (props) => { const { id } = useParams(); + // TODO : Change default state to false + const [isHelping, setIsHelping] = useState(true); + + const isAuthenticated = () => { + true; + }; + + const helpingForms = () => ( +
+
+
+
+ +
+
+
+ + +
+ ); + + const autheticationForms = () => ( +
+
+
+
+ +
+
+ +
+
+
+ + +
+ ); useEffect(() => { props.loadHelp(id); @@ -22,20 +86,16 @@ const HelpPage = (props) => {
{props.help.description}

- -
+
-
- -
-
Em Desenvolvimento...
+ + {isHelping ? (isAuthenticated ? helpingForms : autheticationForms) : null}

@@ -55,6 +115,7 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { loadHelp: (id) => dispatch(loadHelp(id)), + registerVolunteer: (payload) => dispatch(registerVolunteer(payload)), }; }; diff --git a/app/javascript/client/store/index.js b/app/javascript/client/store/index.js index 56cdbc8..e40277f 100644 --- a/app/javascript/client/store/index.js +++ b/app/javascript/client/store/index.js @@ -5,6 +5,7 @@ import { connectRouter, routerMiddleware } from 'connected-react-router'; import { createBrowserHistory } from 'history'; import helpsSlice from './helps/slice'; +import volunteersSlice from './volunteers/slice'; export const history = createBrowserHistory(); @@ -16,6 +17,7 @@ const rootReducer = (history) => combineReducers({ router: connectRouter(history), helps: helpsSlice.reducer, + volunteers: volunteersSlice.reducer, }); const store = configureStore({ diff --git a/app/javascript/client/store/volunteers/actions.js b/app/javascript/client/store/volunteers/actions.js new file mode 100644 index 0000000..49bd72a --- /dev/null +++ b/app/javascript/client/store/volunteers/actions.js @@ -0,0 +1,41 @@ +import axios from 'axios'; +import { push } from 'connected-react-router'; + +import volunteersSlice from './slice'; + +const { actions } = volunteersSlice; + +const registerVolunteer = (payload) => { + return (dispatch, getState) => { + const state = getState(); + + dispatch(actions.registerVolunteerInit()); + + axios.defaults.headers.common['X-CSRF-Token'] = document + .querySelector('meta[name="csrf-token"]') + .getAttribute('content'); + + axios + .post('/api/volunteers', { + volunteer: { + fullname: payload.fullname, + cellphone: payload.cellphone, + email: payload.email, + password: payload.password, + province: payload.province, + county: payload.county, + }, + }) + .then((response) => { + dispatch( + actions.registerVolunteerSuccess({ volunteer: response.data }), + ); + // dispatch(push('/quero-ajudar')); + }) + .catch((error) => { + dispatch(actions.registerVolunteerFailure(error.response.data)); + }); + }; +}; + +export { registerVolunteer }; diff --git a/app/javascript/client/store/volunteers/slice.js b/app/javascript/client/store/volunteers/slice.js new file mode 100644 index 0000000..19b8663 --- /dev/null +++ b/app/javascript/client/store/volunteers/slice.js @@ -0,0 +1,41 @@ +import produce from 'immer'; +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + loading: false, + errors: [], + authenticatedVolunteer: {}, +}; + +const volunteersSlice = createSlice({ + name: 'volunteers', + initialState, + + reducers: { + registerVolunteerInit: (state) => { + return produce(state, (draft) => { + draft.loading = true; + draft.errors = []; + draft.authenticatedVolunteer = {}; + }); + }, + + registerVolunteerSuccess: (state, action) => { + return produce(state, (draft) => { + draft.loading = true; + draft.errors = []; + draft.authenticatedVolunteer = action.payload.volunteer; + }); + }, + + registerVolunteerFailure: (state, action) => { + return produce(state, (draft) => { + draft.loading = false; + draft.errors = action.payload; + draft.authenticatedVolunteer = {}; + }); + }, + }, +}); + +export default volunteersSlice; diff --git a/app/javascript/client/utils/setAuthToken.js b/app/javascript/client/utils/setAuthToken.js new file mode 100644 index 0000000..5271756 --- /dev/null +++ b/app/javascript/client/utils/setAuthToken.js @@ -0,0 +1,11 @@ +import axios from 'axios'; + +const setAuthToken = (token) => { + if (token) { + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + } else { + delete axios.defaults.headers.common['Authorization']; + } +}; + +export default setAuthToken; diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index f3bcce5..699a98e 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,23 +1,24 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Define an application-wide content security policy # For further information see the following documentation # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy -# Rails.application.config.content_security_policy do |policy| -# policy.default_src :self, :https -# policy.font_src :self, :https, :data -# policy.img_src :self, :https, :data -# policy.object_src :none -# policy.script_src :self, :https -# policy.style_src :self, :https -# # If you are using webpack-dev-server then specify webpack-dev-server host -# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? +Rails.application.config.content_security_policy do |policy| + policy.default_src :self, :https + policy.font_src :self, :https, :data + policy.img_src :self, :https, :data + policy.object_src :none + policy.script_src :self, :https + policy.style_src :self, :https, :unsafe_inline + # If you are using webpack-dev-server then specify webpack-dev-server host + policy.connect_src :self, :https, 'http://localhost:3035', 'ws://localhost:3035' if Rails.env.development? -# # Specify URI for violation reports -# # policy.report_uri "/csp-violation-report-endpoint" -# end + # Specify URI for violation reports + # policy.report_uri "/csp-violation-report-endpoint" +end # If you are using UJS then enable automatic nonce generation # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 0000000..f9506aa --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins %r{https?://(.*?)\.sos\.local$} + + resource '*', + headers: :any, + methods: %i[get post put patch delete options head] + end + + allow do + origins %r{https?://(.*?)\.sos\.ao$} + + resource '*', + headers: :any, + methods: %i[get post put patch delete options head] + end +end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..aba98b5 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +domain = Rails.env.production? ? 'sos.ao' : 'sos.local' + +Rails.application.config.session_store :cookie_store, key: '_sos_ao_session', domain: domain diff --git a/spec/requests/api/volunteers_request_spec.rb b/spec/requests/api/volunteers_request_spec.rb index 56c13c1..02119d8 100644 --- a/spec/requests/api/volunteers_request_spec.rb +++ b/spec/requests/api/volunteers_request_spec.rb @@ -17,7 +17,7 @@ let(:params) { { auth: { email: volunteer.email, password: 'secret$123' } } } it { expect(response.content_type).to eq('application/json; charset=utf-8') } - it { expect(response).to have_http_status(:ok) } + it { expect(response).to have_http_status(:created) } it { expect(session[:volunteer_id]).to eq volunteer.id }