新建Auth界面
类似checkourder的表单;
对input的value和state进行双向绑定;
且加入有效检查‘
import React,{Component} from 'react';
import Input from '../../components/UI/Input/Input';
import Button from '../../components/UI/Button/Button';
import classes from './Auth.css';
class Auth extends Component{
state={
controls:{
email: {
elementType:'input',
elementConfig:{
type:'email',
placeholder:'Mail Address'
},
value:'',
validation:{
required:true,
isEmail:true
},
valid:false,
touched:false
},
password: {
elementType:'input',
elementConfig:{
type:'password',
placeholder:'Password'
},
value:'',
validation:{
required:true,
minLength:6
},
valid:false,
touched:false
}
}
}
checkValidity(value, rules){
let isValid= true;
if(rules.required&&isValid){
isValid= value.trim()!==''; //.trim()可以可以防止空格
}
if(rules.minLength&&isValid){
isValid= value.length>=rules.minLength;
}
if(rules.maxLength&&isValid){
isValid= value.length<=rules.maxLength;
};
return isValid;
};
inputChangedHandler = (event, controlName)=>{
const updatedControls = {
...this.state.controls,
[controlName]:{
...this.state.controls[controlName],
value:event.target.value,
//使用检查函数
valid:this.checkValidity(event.target.value, this.state.controls[controlName].validation),
touched:true //标记为输入过
}
};
this.setState({controls:updatedControls});
}
render(){
const formElementArray = [];
for(let key in this.state.controls){ //遍历每个name
formElementArray.push({
id:key,
config:this.state.controls[key]
});
};
const form = formElementArray.map(formElement=>(
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
changed={(event)=>this.inputChangedHandler(event,formElement.id)}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation&& formElement.config.touched}
/>
));
return(
<div className={classes.Auth}>
<form>
{form}
<Button btnType="Success">SUBMIT</Button>
</form>
</div>
);
};
};
export default Auth;
在navigtaion中加入此component——link;
import React from 'react';
import classes from './NavigationItems.css';
import NavigationItem from './NavigationItem/NavigationItem';
const navigationItems = () => (
<ul className={classes.NavigationItems}>
<NavigationItem link="/" exact>Burger Builder</NavigationItem>
<NavigationItem link="/orders">Orders</NavigationItem>
<NavigationItem link="/auth">Authenticate</NavigationItem>
</ul>
);
export default navigationItems;
在app.js中加入此route;
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import Layout from './hoc/Layout/Layout';
import BurgerBuilder from './containers/BurgerBuilder/BurgerBuilder';
import Checkout from './containers/Checkout/Checkout';
import Orders from './containers/Orders/Orders';
import Auth from './containers/Auth/Auth';
class App extends Component {
render () {
return (
<div>
<Layout>
<Switch>
<Route path="/checkout" component={Checkout}/>
<Route path="/orders" component={Orders}/>
<Route path="/auth" component={Auth}/>
<Route path="/" exact component={BurgerBuilder}/>
</Switch>
</Layout>
</div>
);
}
}
export default App;
设置form的onSubmit参数
Auth/js
引入action和connect;
import * as actions from '../../store/actions/index';
import {connect} from 'react-redux';
form加入onsubmit——调用函数;
submitHandler = (event)=>{
event.preventDefault();
this.props.onAuth(this.state.controls.email.value,this.state.controls.password.value);
}
return(
<div className={classes.Auth}>
<form onSubmit={this.submitHandler}>
{form}
<Button btnType="Success">SUBMIT</Button>
</form>
</div>
);
将redux中的action转为props传入此js;
const mapDispatchToPoprs =dispatch=>{
return {
onAuth:(email,password)=>dispatch(actions.auth(email,password))
};
};
export default connect(null,mapDispatchToPoprs)(Auth);
使用firebase 的 api,进行signin和signup
https://firebase.google.com/docs/reference/rest/auth#section-create-email-password
在auth中执行异步调用
//用于auth的异步函数
export const auth = (email, password) =>{
return dispatch=>{
dispatch(authStart());
const authData= {
email: email,
password:password,
returnSecureToken: true
}
axios.post('https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=',authData)
.then(response=>{
console.log(response);
dispatch(authSuccess(response.data));
})
.catch(err=>{
console.log(err);
dispatch(authFail(err));
})
};
};
成功完成post操作;
控制sign in
设定新状态——判断当前是signup还是signin
switchAuthModeHandler = ()=>{
this.setState(prevState=>{
return {isSignup: !prevState.isSignup}
})
}
<Button
clicked={this.switchAuthModeHandler}
btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
根据state中的signin标记,进行不同的url调用
//用于auth的异步函数
export const auth = (email, password, isSignup) =>{ //传入需要signup还是signin
return dispatch=>{
dispatch(authStart());
const authData= {
email: email,
password:password,
returnSecureToken: true
};
let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
if(!isSignup){
url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
}
axios.post(url,authData)
.then(response=>{
console.log(response);
dispatch(authSuccess(response.data));
})
.catch(err=>{
console.log(err);
dispatch(authFail(err));
})
};
};
将signin后从db中得到的用于token和id保存在auth的state中
调用authsuccess时传入id和token;
return时保存在redux的state中;
import * as actionTypes from './actionsTypes';
import axios from 'axios';
export const authStart = ()=>{ //用于loading spinner
return {
type:actionTypes.AUTH_STRAT
};
};
export const authSuccess = (token,userId)=>{
return{
type:actionTypes.AUTH_SUCCESS,
idToken:token,
userId:userId
};
};
export const authFail = (error)=>{
return{
type: actionTypes.AUTH_FAIL,
error:error
};
};
//用于auth的异步函数
export const auth = (email, password, isSignup) =>{ //传入需要signup还是signin
return dispatch=>{
dispatch(authStart());
const authData= {
email: email,
password:password,
returnSecureToken: true
};
let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
if(!isSignup){
url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
}
axios.post(url,authData)
.then(response=>{
console.log(response);
dispatch(authSuccess(response.data.idToken,response.data.localId));
})
.catch(err=>{
console.log(err);
dispatch(authFail(err));
})
};
};
在signin&up等待异步调用的时候加入spinner
在Auth中加入reducer和store
import * as actions from '../../store/actions/index';
import {connect} from 'react-redux';
import Spinner from '../../components/UI/Spinner/Spinner';
判断loading是否为true;
if(this.props.loading){
form=<Spinner/>;
}
将spinner加入return中;
return(
<div className={classes.Auth}>
{errorMessage}
<form onSubmit={this.submitHandler}>
{form}
<Button btnType="Success">SUBMIT</Button>
</form>
<Button
clicked={this.switchAuthModeHandler}
btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
</div>
);
需要传入的state和action;
//需要得到reducer中的state
//进行signup & in时都会改变loading,调用loading即可
const mapStateToProps = state=>{
return {
loading:state.auth.loading,
error:state.auth.error
};
};
const mapDispatchToPoprs =dispatch=>{
return {
onAuth:(email,password,isSignup)=>dispatch(actions.auth(email,password,isSignup))
};
};
export default connect(mapStateToProps,mapDispatchToPoprs)(Auth);
错误处理
firebase提供的错误信息;
将props中保存的error在auth页面中提出;
let errorMessage = null;
if(this.props.error){
errorMessage = (
<p>{this.props.error.message}</p> //从firebase获取的error
)
};
return(
<div className={classes.Auth}>
{errorMessage}
<form onSubmit={this.submitHandler}>
{form}
<Button btnType="Success">SUBMIT</Button>
</form>
<Button
clicked={this.switchAuthModeHandler}
btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
</div>
);
保存error——action
axios中得到err.response
得到firebase提供的error.data.error,里面的message即为错误信息;
//用于auth的异步函数
export const auth = (email, password, isSignup) =>{ //传入需要signup还是signin
return dispatch=>{
dispatch(authStart());
const authData= {
email: email,
password:password,
returnSecureToken: true
};
let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
if(!isSignup){
url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
}
axios.post(url,authData)
.then(response=>{
console.log(response);
dispatch(authSuccess(response.data.idToken,response.data.localId));
})
.catch(err=>{
dispatch(authFail(err.response.data.error)); //axios提供的err调用方法
});
};
};
设定Logout——定时器
actiontype
//logout
export const AUTH_LOGOUT = 'AUTH_LOGOUT';
action
export const logout=()=>{
return {
type: actionTypes.AUTH_LOGOUT
};
};
export const checkAuthTimeout=(expirationTime)=>{
return dispatch=>{
setTimeout(() => {
dispatch(logout());
}, expirationTime * 1000); //ms 转为 s
};
};
异步访问——成功时设置定时器;
axios.post(url,authData)
.then(response=>{
console.log(response);
dispatch(authSuccess(response.data.idToken,response.data.localId));
//同时设置登录计时
dispatch(checkAuthTimeout(response.data.expiresIn));
})
.catch(err=>{
dispatch(authFail(err.response.data.error)); //axios提供的err调用方法
});
设置firebase中的访问条件
可以访问ingredients;
需要auth才能访问orders;
{
"rules": {
"ingredients":{
".read": true,
".write": true
},
"orders":{
".read":"auth != null",
".write":"auth != null"
}
}
}
在获取token后的post和get order操作中,传入auth=token
order.js
token参数都为上一层调用connect后从reducer的state中传入;
或者也可在dispatch旁加入getState参数,得到当前的state,调用state.token即可;
export const fetchOrders= (token)=>(dispatch)=>{ //actioncreator函数——返回一个action
dispatch(fetchOrdersStart());
//加入auth操作
axios.get('/orders.json?auth='+token)
.then(res=>{
const fetchOrders=[];
for(let key in res.data){
fetchOrders.push({
...res.data[key],
id:key
});
}
dispatch(fetchOrdersSuccess(fetchOrders));
})
.catch(error=>{
dispatch(fetchOrdersFailed(error));
});
};
//async 不返回一个action,返回一个调用action的异步函数
export const purchaseBurger = (orderData, token) => {
return dispatch=>{
dispatch(purchaseBurgerStart());//在异步调用前改变loading——要用dispatch包裹
axios.post( '/orders.json?auth='+token, orderData )
.then( response => {
console.log(response.data); //name才是真正的id
dispatch(purchaseBurgerSuccess(response.data.name,orderData));
} )
.catch( error => {
dispatch(purchaseBurgerFail(error));
} );
};
};
设置NAV-authenticate在获取token后变为NAV-Logout
import React from 'react';
import classes from './NavigationItems.css';
import NavigationItem from './NavigationItem/NavigationItem';
//只有class extends component 才能用connect
const navigationItems = (props) => (
<ul className={classes.NavigationItems}>
<NavigationItem link="/" exact>Burger Builder</NavigationItem>
<NavigationItem link="/orders">Orders</NavigationItem>
{props.isAuthenticated
?<NavigationItem link="/logout">Logout</NavigationItem>
:<NavigationItem link="/auth">Authenticate</NavigationItem>
}
</ul>
);
export default navigationItems;
选取调用该navigationitems的container,connect该container,并向下传入auth状态,根据状态显示logout或authentic;
layout——》toolbar——》navigationitems
Logout 后redirect回主界面+清空token
Logout.js
import React,{Component} from 'react';
import * as actions from '../../../store/actions/index';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
class Logout extends Component {
componentDidMount(){
this.props.onLogout();
};
render(){
return(
<Redirect path="/"/>
);
};
};
const mapDispatchToProps = dispatch=>{
return{
onLogout: ()=>dispatch(actions.logout())
};
}
export default connect(null,mapDispatchToProps)(Logout);
App.js中加入该route
<Route path="/logout" component={Logout}/>
问题:并没有清除state中保存的order内容
在signin前隐藏orders
Navigtaionitems.js
<NavigationItem link="/" exact>Burger Builder</NavigationItem>
{
props.isAuthenticated
?<NavigationItem link="/orders">Orders</NavigationItem>
:null
}
在signin成功后redirect到ingredients
Auth .js
let authRedirect = null;
if(this.props.isAuthenticated){
authRedirect=<Redirect to='/'/>
};
return(
<div className={classes.Auth}>
{authRedirect}
{errorMessage}
<form onSubmit={this.submitHandler}>
{form}
<Button btnType="Success">SUBMIT</Button>
</form>
<Button
clicked={this.switchAuthModeHandler}
btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
</div>
);
在Sign In 之前,ingredients页面显示signup ,并重定向到注册页面
BurgerBuilder.js
根据auth来定义该按钮的操作——进入purchasing 或者 重定向到注册/登录页面;
purchaseHandler = () => {
if(this.props.isAuthenticated){
this.setState( { purchasing: true } );
}else{
this.props.history.push('/auth');
}
}
BuildControls.js
根据传入的autrh显示按钮的输出内容;
<button
className={classes.OrderButton}
disabled={!props.purchasable}
onClick={props.ordered}>{props.isAuth?'ORDER NOW':'SIGN UP TO ORDER'}</button>
signin&up界面重定向的逻辑:
- 若从建立汉堡的signup to cotinue界面进入,则sign in 后直接定向到checkout界面;
- 若从auth进入,sign in 后直接定向到建立汉堡界面;
状态1:auth.js
state保存当前需要重定向的地址;
authRedirectPath: '/'
函数
const setAuthRedirectPath = (state,action)=>{
return updateObejct(state,{authRedirectPath:action.path})
};
reducer:
case actionTypes.SET_AUTH_REDIRECT_PATH: return setAuthRedirectPath(state,action);
action(用于在container中被调用):
//将path传入reducer,作为auth的state
export const setAuthRedirectPath = (path)=>{
return {
type:actionTypes.SET_AUTH_REDIRECT_PATH,
path:path
};
};
Burgerbuilder.js中
若为点击sign up to purchase,则将authstate中国的path改为checkout;
purchaseHandler = () => {
if(this.props.isAuthenticated){
this.setState( { purchasing: true } );
}else{
this.props.onSetAuthRedirectPath('/checkout');
this.props.history.push('/auth');
}
};
状态2:buildingburger——判断是否建立汉堡
burgerbuilder——state;
在添加和删除时改为true;
在返回构建页面时,改为false;
building:false //添加/删除后改为true
最终的条件判断——Auth.js
当满足 (没有重新回到build页面&&从build页面的signup按钮过来)时,登录后直接定位到checkout界面;
componentDidMount(){
//没有buildburger并且访问了该页面并且初始path不为‘/’
if(!this.props.buildingBurger && this.props.authRedirectPath!=='/'){
this.props.onSetAuthRedirectPath(); //将默认path改为'.'
}
};