-
Notifications
You must be signed in to change notification settings - Fork 0
# Step 07 — Users component
Gerald Goh edited this page Aug 11, 2020
·
1 revision
Gravatar stands for Globally Recognized Avatar. It is globally recognized because millions of people and websites use them. Most popular applications like WordPress have built-in support for Gravatar. When a user leaves a comment (with email) on a site that supports Gravatar, it pulls their Globally Recognized Avatar from Gravatar servers. Then that picture is shown next to the comment. This allows each commenter to have their identity through out the world wide web.
For the purpose of this project, Gravatar will be used profile pictures.
# app/helpers/users_helper.rb
module UsersHelper
# Returns the Gravatar for the given user.
def gravatar_for(user, size: 80)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
./app/javascript/components/home/Signup.jsx
class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
email: '',
password: '',
password_confirmation: '',
units: '',
target: '',
errors: {},
}
}
.
.
- handleChange to pass values to set state
- onSubmit send user inputs to users controlller (see
config/routes.rb
)
./app/javascript/components/Signup.jsx
.
.
handleChange(e) {
this.setState(
{
[e.target.id]: e.target.value
}
)
}
onSubmit(e) {
e.preventDefault();
let { name, email, password, password_confirmation, units, target } = this.state;
axios.post("/api/users", {
name, email, password, password_confirmation, units, target,
})
.then(response => response.data)
.then(response => {
if (response.code == 400) {
console.log(response);
this.setState({
errors: response.errors,
})
} else if (response.code == 200) {
this.setState({
name: "",
email: "",
password: "",
password_confirmation: '',
units: "",
target: "",
errors: {},
})
}
}
)
}
.
.
- Render DOM
- Listens for input and click events to pass functions on with argument.
./app/javascript/components/Signup.jsx
.
.
render() {
let { name, email, password, password_confirmation, units, target } = this.state;
return (
<div className="container text-content sign-in-up">
<div className="row justify-content-center">
<div className="col-md-4 col-md-offset-4">
<br />
{/* Nav tabs */}
<div className="text-center">
<div className="btn-group">
<a href="#new" role="tab" data-toggle="tab" className="big btn btn-primary">
<i className="fa fa-plus" />
{' '}
Create Account
</a>
<a href="#user" role="tab" data-toggle="tab" className="big btn btn-success">
<i className="fa fa-user" />
{' '}
Login
</a>
</div>
</div>
<p className="click2select">Tap to select</p>
<div className="tab-content">
<div className="tab-pane fade in active" id="new">
<br />
<fieldset>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-user" />
<input
className="form-control input-lg"
placeholder="Name"
type="text"
id="name"
value={name}
onChange={e => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-envelope" />
<input
className="form-control input-lg"
placeholder="Email"
type="text"
id="email"
value={email}
onChange={e => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-key" />
<input
className="form-control input-lg"
placeholder="Password"
type="password"
id="password"
value={password}
onChange={e => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-key" />
<input
className="form-control input-lg"
placeholder="Password Confirmation"
type="password"
id="password_confirmation"
value={password_confirmation}
onChange={e => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<select
className="custom-select custom-select-md"
id="units"
value={units}
onChange={e => this.handleChange(e)}
>
<option>Choose monthly units</option>
<option value="1800">1800</option>
<option value="2100">2100</option>
<option value="2400">2400</option>
<option value="2700">2700</option>
<option value="3000">3000</option>
</select>
</div>
<div className="form-group">
<select
className="custom-select custom-select-md"
id="target"
value={target}
onChange={e => this.handleChange(e)}
>
<option>Choose month target savings</option>
<option value="5">5%</option>
<option value="10">10%</option>
<option value="15">15%</option>
<option value="20">20%</option>
<option value="25">25%</option>
</select>
</div>
</fieldset>
<hr />
<div className="tab-content">
<div className="tab-pane active text-center" id="pp">
<button
className="btn btn-primary btn-lg btn-block"
type="submit"
onClick={(e) => this.onSubmit(e)}
>
<i className="fa fa-plus" />
{' '}
Create Account
</button>
</div>
</div>
</div>
<Signin />
</div>
</div>
</div>
</div>
)
}
.
.
- Added
Axios
- Added
Redux connect
- Added dispatch
SIGNEDIN
action that stores state function - Added eventlisteners for click events and input
- Added constructors
- Changed function to class
./app/javascript/components/home/Signin.jsx
/* eslint-disable react/destructuring-assignment */
/* eslint-disable react/no-unused-state */
import React from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { SIGNEDIN } from '../../actions';
class Signin extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: {},
success: true,
};
}
onSubmit(e) {
e.preventDefault();
const { email, password } = this.state;
axios.post('/api/users/sessions', {
user: {
email,
password,
},
})
.then((response) => response.data)
.then((response) => {
if (response.code === 200) {
this.props.login(response.user.name);
this.props.history.push('/addreading');
} else if (response.code === 400) {
this.setState({
errors: response.errors,
});
}
});
}
handleChange(e) {
this.setState(
{
[e.target.id]: e.target.value,
},
);
}
render() {
const { email, password } = this.state;
return (
<div className="tab-pane" id="user">
<br />
<fieldset>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-envelope" />
<input
className="form-control input-lg"
placeholder="Email"
type="text"
id="email"
value={email}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-key" />
<input
className="form-control input-lg"
placeholder="Password"
type="password"
id="password"
value={password}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
</fieldset>
<br />
<div className=" text-center">
<button
className="btn btn-primary btn-success"
type="button"
onClick={(e) => this.onSubmit(e)}
>
<i className="fa fa-user" />
{' '}
LOGIN
</button>
</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
login: ((name) => {
dispatch(SIGNEDIN(name));
}),
});
Signin.propTypes = {
login: PropTypes.func.isRequired,
// eslint-disable-next-line react/forbid-prop-types
history: PropTypes.object.isRequired,
};
export default withRouter(connect(null, mapDispatchToProps)(Signin));
./app/javascript/components/home/Signup.jsx
/* eslint-disable react/no-unused-state */
/* eslint-disable camelcase */
import React from 'react';
import axios from 'axios';
class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
email: '',
password: '',
password_confirmation: '',
units: '',
target: '',
errors: {},
};
}
onSubmit(e) {
e.preventDefault();
const {
name, email, password, password_confirmation, units, target,
} = this.state;
axios.post('/api/users', {
name, email, password, password_confirmation, units, target,
})
.then((response) => response.data)
.then((response) => {
if (response.code === 400) {
this.setState({
errors: response.errors,
});
} else if (response.code === 200) {
this.setState({
name: '',
email: '',
password: '',
password_confirmation: '',
units: '',
target: '',
errors: {},
});
}
});
}
handleChange(e) {
this.setState(
{
[e.target.id]: e.target.value,
},
);
}
render() {
const {
name, email, password, password_confirmation, units, target,
} = this.state;
return (
<div className="tab-content">
<div id="new">
<br />
<fieldset>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-user" />
<input
className="form-control input-lg"
placeholder="Name"
type="text"
id="name"
value={name}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-envelope" />
<input
className="form-control input-lg"
placeholder="Email"
type="text"
id="email"
value={email}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-key" />
<input
className="form-control input-lg"
placeholder="Password"
type="password"
id="password"
value={password}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<div className="right-inner-addon">
<i className="fa fa-key" />
<input
className="form-control input-lg"
placeholder="Password Confirmation"
type="password"
id="password_confirmation"
value={password_confirmation}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div className="form-group">
<select
className="custom-select custom-select-md"
id="units"
value={units}
onChange={(e) => this.handleChange(e)}
>
<option>Choose monthly units</option>
<option value="1800">1800</option>
<option value="2100">2100</option>
<option value="2400">2400</option>
<option value="2700">2700</option>
<option value="3000">3000</option>
</select>
</div>
<div className="form-group">
<select
className="custom-select custom-select-md"
id="target"
value={target}
onChange={(e) => this.handleChange(e)}
>
<option>Choose month target savings</option>
<option value="5">5%</option>
<option value="10">10%</option>
<option value="15">15%</option>
<option value="20">20%</option>
<option value="25">25%</option>
</select>
</div>
</fieldset>
<hr />
<div className="tab-content">
<div className="tab-pane active text-center" id="pp">
<button
className="btn btn-primary btn-lg btn-block"
type="submit"
onClick={(e) => this.onSubmit(e)}
>
<i className="fa fa-plus" />
{' '}
Create Account
</button>
</div>
</div>
</div>
</div>
);
}
}
export default Signup;
.app/javascript/containers/Home.jsx
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import Head from '../components/home/Head';
import Signin from '../components/home/Signin';
import Signup from '../components/home/Signup';
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
section: <Signin />,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
const sector = e.target.value;
if (sector === 'Signup') {
this.setState(() => ({
section: <Signup />,
}));
} else {
this.setState(() => ({
section: <Signin />,
}));
}
}
render() {
const sector = this.state.section;
return (
<div>
<Head />
<div className="container text-content sign-in-up">
<div className="row justify-content-center">
<div className="col-md-4 col-md-offset-4">
<br />
{/* Nav tabs */}
<div className="text-center">
<div className="btn-group">
<button
type="button"
value="Signup"
className="big btn btn-primary"
onClick={(e) => this.handleClick(e, 'value')}
>
<i className="fa fa-plus" />
{' '}
Create Account
</button>
<button
type="button"
value="Signin"
className="big btn btn-success"
onClick={(e) => this.handleClick(e, 'value')}
>
<i className="fa fa-user" />
{' '}
Login
</button>
</div>
</div>
<p className="click2select">Tap to select</p>
{sector}
</div>
</div>
</div>
</div>
);
}
}
export default Home;
$ rails generate controller api/v1/users index create show -j=false -y=false --skip-template-engine --no-helper
create app/controllers/users_controller.rb
invoke erb
create app/views/users
invoke test_unit
create test/controllers/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/users.scss
- To accept params from react
app/controllers/users_controller.rb
class Api::V1::UsersController < ApplicationController
skip_before_action :verify_authenticity_token
def create
@user = User.new(
name: params[:name],
email: params[:email],
password: params[:password],
password_confirmation: params[:password_confirmation],
units: params[:units],
target: params[:target],
)
if @user.save
render json: {
code: 200
}
else
render json: {
code: 400,
errors: @user.errors.messages
}
end
end
def index
@users = User.all
if @users
render json: {
code: 200,
data: User.all.as_json
}
else
render json: @users.errors
end
end
def find_user
@users = User.all
@users = User.find(params[:id])
if @users
render json: {
code: 200,
data: @users.as_json
}
else
render json: @users.errors
end
end
def destroy; end
end
$ rails generate controller api/v1/users/sessions -j=false -y=false --skip-template-engine --no-helper
create app/controllers/users/sessions_controller.rb
invoke erb
create app/views/users/sessions
invoke test_unit
create test/controllers/users/sessions_controller_test.rb
invoke helper
create app/helpers/users/sessions_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/users/sessions.scss
- To accept params from react
app/controllers/api/v1/users/sessions_controller.rb
class Api::V1::Users::SessionsController < ApplicationController
skip_before_action :verify_authenticity_token
def create
@user = User.find_by(email: params[:user][:email])
if @user&.authenticate(params[:user][:password])
login @user
render json: {
code: 200,
user: {
name: @user.name
}
}
else
render json: {
code: 400,
errors: { Error: ['Email and/or Password does not match !'] }
}
end
end
def destroy
@current_user = nil
session.delete(:id)
end
private
def user_params
params[:user].permit(:email, :password)
end
end
Added new file and content
/app/javascript/actions/index.jsx
const SIGNEDIN = (name) => ({
type: 'SIGNEDIN',
name,
});
const SIGNEDOUT = () => ({ type: 'SIGNEDOUT' });
export { SIGNEDIN, SIGNEDOUT };
/app/javascript/reducer/sessions.jsx\
const sessionReducer = (state = { siginin: false, name: '' }, action) => {
switch (action.type) {
case 'SIGNEDIN':
return { login: true, name: action.name };
case 'SIGNEDOUT':
return { login: false, name: '' };
default:
return state;
}
};
export default sessionReducer;
- Add redux store and provider
app/javscript/routes/index.jsx
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
import persistState from 'redux-localstorage';
import Home from '../containers/Home';
import Readings from '../containers/Readings';
import AddReading from '../containers/AddReading';
import Reading from '../containers/Reading';
import reducer from '../reducer/sessions';
const store = createStore(
reducer, compose(persistState()),
);
export default (
<Provider store={store}>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/addreading" component={AddReading} />
<Route path="/readings" component={Readings} />
<Route path="/reading" component={Reading} />
</Switch>
</Router>
</Provider>
);
export { store };
app/controller/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def login(user)
session[:id] = user.id
end
def current_user
@current_user ||= session[:id] && User.find_by_id(session[:id])
end
def logged_in?
render json: { code: 401 } unless current_user
end
end