Front-End Web Dev. with React Week 4
Assignment Week 4
About Component
import React from 'react';
import { Breadcrumb, BreadcrumbItem, Card, CardBody, CardHeader, Media } from 'reactstrap';
import { Link } from 'react-router-dom';
import { baseUrl } from "../shared/baseUrl";
import { FadeTransform, Fade, Stagger } from 'react-animation-components';
import { Loading } from "./LoadingComponent";
function RenderLeader({ leader }) {
return (
<Media className="mt-5">
<Media left className="mr-5">
<Media object src={baseUrl + leader.image} alt={leader.name} />
</Media>
<Media body>
<Media heading>{leader.name}</Media>
<p>{leader.designation}</p>
{leader.description}
</Media>
</Media>
);
}
function RenderContent({ leaders, isLoading, errMess }) {
if (isLoading) {
return <Loading />;
} else if (errMess) {
return <h4>{errMess}</h4>;
} else
return (
<Stagger in>
{leaders.map(leader => (
<Fade in key={leader.id}>
<RenderLeader key={leader.id} leader={leader} />
</Fade>
))}
</Stagger>
);
}
function About(props) {
// const leaders = props.leaders.map((leader) => {
// return (
// <RenderLeader
// leader={leader}
// isLoading={props.leaderLoading}
// errMess={props.leaderErrMess}
// />
// );
// });
return (
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem><Link to="/home">Home</Link></BreadcrumbItem>
<BreadcrumbItem active>About Us</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>About Us</h3>
<hr />
</div>
</div>
<div className="row row-content">
<div className="col-12 col-md-6">
<h2>Our History</h2>
<p>Started in 2010, Ristorante con Fusion quickly established itself as a culinary icon par excellence in Hong Kong. With its unique brand of world fusion cuisine that can be found nowhere else, it enjoys patronage from the A-list clientele in Hong Kong. Featuring four of the best three-star Michelin chefs in the world, you never know what will arrive on your plate the next time you visit us.</p>
<p>The restaurant traces its humble beginnings to <em>The Frying Pan</em>, a successful chain started by our CEO, Mr. Peter Pan, that featured for the first time the world's best cuisines in a pan.</p>
</div>
<div className="col-12 col-md-5">
<Card>
<CardHeader className="bg-primary text-white">Facts At a Glance</CardHeader>
<CardBody>
<dl className="row p-1">
<dt className="col-6">Started</dt>
<dd className="col-6">3 Feb. 2013</dd>
<dt className="col-6">Major Stake Holder</dt>
<dd className="col-6">HK Fine Foods Inc.</dd>
<dt className="col-6">Last Year's Turnover</dt>
<dd className="col-6">$1,250,375</dd>
<dt className="col-6">Employees</dt>
<dd className="col-6">40</dd>
</dl>
</CardBody>
</Card>
</div>
<div className="col-12">
<Card>
<CardBody className="bg-faded">
<blockquote className="blockquote">
<p className="mb-0">You better cut the pizza in four pieces because
I'm not hungry enough to eat six.</p>
<footer className="blockquote-footer">Yogi Berra,
<cite title="Source Title">The Wit and Wisdom of Yogi Berra,
P. Pepe, Diversion Books, 2014</cite>
</footer>
</blockquote>
</CardBody>
</Card>
</div>
</div>
<div className="row row-content">
<div className="col-12">
<h2>Corporate Leadership</h2>
</div>
<div className="col-12">
<div className="row">
<Media list>
<RenderContent
leaders={props.leaders}
isLoading={props.leaderLoading}
errMess={props.leaderErrMess}
/>
</Media>
</div>
</div>
</div>
</div>
);
}
export default About;
Action Creators
import * as ActionTypes from './ActionTypes';
// import { DISHES } from '../shared/dishes';
import { baseUrl } from '../shared/baseUrl';
/**.......... Comment............................ */
export const addComment = (comment) => ({
type: ActionTypes.ADD_COMMENT,
payload: comment
});
export const postComment = (dishId, rating, author, comment)=> (dispatch)=> {
const newComment = {
dishId: dishId,
rating: rating,
author: author,
comment: comment,
}
newComment.date = new Date().toISOString();
return fetch( baseUrl + 'comments', {
method: 'POST',
body: JSON.stringify(newComment),
headers: {
'Content-type': 'application/json'
},
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText)
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error(error.message);
throw errmess;
}
)
.then( response => response.json() )
.then( response => dispatch(addComment(response)) )
.catch(error => {
console.log('Post Comments', error.message);
alert('Comment could not be posted\nError'+ error.message);
});
}
/**.......... Feedback ............................ */
// export const addFeedback = (feedback) => ({
// type: ActionTypes.ADD_FEEDBACK,
// payload: feedback
// });
export const postFeedback = (firstname, lastname, telnum, email, agree, contactType, message) => (dispatch) => {
const newFeedback = {
firstname: firstname,
lastname: lastname,
telnum: telnum,
email: email,
agree: agree,
contactType: contactType,
message: message,
}
newFeedback.date = new Date().toISOString();
return fetch(baseUrl + 'feedback', {
method: 'POST',
body: JSON.stringify(newFeedback),
headers: {
'Content-type': 'application/json'
},
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText)
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error(error.message);
throw errmess;
}
)
.then(response => response.json())
.then(response => alert(JSON.stringify(response)))
.catch(error => {
console.log('Post Comments', error.message);
alert('Comment could not be posted\nError' + error.message);
});
}
/**.......... Dishes............................ */
//// thunk: function returns an func
export const fetchDishes = () => (dispatch) => {
dispatch(dishesLoading(true));
return fetch(baseUrl + 'dishes')
.then(response => {
if (response.ok)
{
return response;
}
else
{
var error = new Error('Error ' + response.status + ': '+ response.statusText )
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error( error.message );
throw errmess;
})
.then(response => response.json())
.then(dishes => dispatch(addDishes(dishes)))
.catch(error => dispatch(dishesFailed(error.message) ) );
}
export const dishesLoading = () => ({
type: ActionTypes.DISHES_LOADING
});
export const dishesFailed = (errmess) => ({
type: ActionTypes.DISHES_FAILED,
payload: errmess
});
//function returns an action
export const addDishes = (dishes) => ({
type: ActionTypes.ADD_DISHES,
payload: dishes
});
/**.......... Comments............................ */
//// thunk: function returns an func
export const fetchComments = () => (dispatch) => {
return fetch(baseUrl + 'comments')
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText)
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(comments => dispatch(addComments(comments)))
.catch(error => dispatch(commentsFailed(error.message)));
};
export const commentsFailed = (errmess) => ({
type: ActionTypes.COMMENTS_FAILED,
payload: errmess
});
export const addComments = (comments) => ({
type: ActionTypes.ADD_COMMENTS,
payload: comments
});
/**.......... promos............................ */
export const fetchPromos = () => (dispatch) => {
dispatch(promosLoading());
return fetch(baseUrl + 'promotions')
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText)
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(promos => dispatch(addPromos(promos)))
.catch(error => dispatch(promosFailed(error.message)));
}
export const promosLoading = () => ({
type: ActionTypes.PROMOS_LOADING
});
export const promosFailed = (errmess) => ({
type: ActionTypes.PROMOS_FAILED,
payload: errmess
});
export const addPromos = (promos) => ({
type: ActionTypes.ADD_PROMOS,
payload: promos
});
/**.......... leaders............................ */
export const fetchLeaders = () => (dispatch) => {
dispatch( leadersLoading() );
return fetch( baseUrl + 'leaders')
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText)
error.response = response;
throw error;
}
},
////if no responmse from server
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then( leaders => dispatch( addLeaders(leaders) ) )
.catch(error => dispatch(leadersFailed(error.message)));
}
export const leadersLoading = () => ({
type: ActionTypes.LEADERS_LOADING
});
export const leadersFailed = (errmess) => ({
type: ActionTypes.LEADERS_FAILED,
payload: errmess
});
export const addLeaders = (leaders) => ({
type: ActionTypes.ADD_LEADERS,
payload: leaders
});
Action Types
export const ADD_COMMENT = 'ADD_COMMENT';
export const ADD_FEEDBACK = 'ADD_FEEDBACK';
export const DISHES_LOADING = 'DISHES_LOADING';
export const DISHES_FAILED = 'DISHES_FAILED';
export const ADD_DISHES = 'ADD_DISHES';
export const ADD_COMMENTS = 'ADD_COMMENTS';
export const COMMENTS_FAILED ='COMMENTS_FAILED';
export const PROMOS_LOADING ='PROMOS_LOADING';
export const ADD_PROMOS = 'ADD_PROMOS';
export const PROMOS_FAILED = 'PROMOS_FAILED';
export const LEADERS_LOADING ='LEADERS_LOADING';
export const ADD_LEADERS = 'ADD_LEADERS';
export const LEADERS_FAILED = 'LEADERS_FAILED';
Contact Component
import React, {Component} from 'react';
import { Breadcrumb, BreadcrumbItem, Button,
Label, Col, Row } from 'reactstrap';
import { Link } from 'react-router-dom';
import { Control, LocalForm, Form, Errors, actions } from 'react-redux-form';
//// validators
const required = (val) => val && val.length; //value > 0
const maxLength = (len) => (val) => !(val) || (val.length <= len);
const minLength = (len) => (val) => (val) &&( val.length >= len);
const isNumber = (val) => !isNaN(Number(val));
const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val);
class Contact extends Component{
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(values){
// console.log("Current State is: " + JSON.stringify( values ) );
// alert("Current State is: " + JSON.stringify( values ) );
this.props.postFeedback( values.firstname, values.lastname, values.telnum, values.email, values.agree, values.contactType, values.message );
this.props.resetFeedbackForm();
}
render(){
return (
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem>
<Link to="/home">Home</Link>
</BreadcrumbItem>
<BreadcrumbItem active>
Contact Us
</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>Contact Us</h3>
<hr />
</div>
</div>
<div className="row row-content">
<div className="col-12">
<h3>Location Information</h3>
</div>
<div className="col-12 col-sm-4 offset-sm-1">
<h5>Our Address</h5>
<address>
121, Clear Water Bay Road<br />
Clear Water Bay, Kowloon<br />
HONG KONG<br />
<i className="fa fa-phone"></i>: +852 1234 5678<br />
<i className="fa fa-fax"></i>: +852 8765 4321<br />
<i className="fa fa-envelope"></i>: <a href="mailto:confusion@food.net">confusion@food.net</a>
</address>
</div>
<div className="col-12 col-sm-6 offset-sm-1">
<h5>Map of our Location</h5>
</div>
<div className="col-12 col-sm-11 offset-sm-1">
<div className="btn-group" role="group">
<a role="button" className="btn btn-primary" href="tel:+85212345678"><i className="fa fa-phone"></i> Call</a>
<a role="button" className="btn btn-info"><i className="fa fa-skype"></i> Skype</a>
<a role="button" className="btn btn-success" href="mailto:confusion@food.net"><i className="fa fa-envelope-o"></i> Email</a>
</div>
</div>
</div>
<div className="row row-content">
<div className="col-12">
<h3> Send us your feedback</h3>
</div>
<div className="col-12 col-md-9">
<Form model="feedback" onSubmit={(values) => this.handleSubmit(values)} resetOnSubmit={true}>
{/* firstname */}
<Row className="form-group">
<Label htmlFor="firstName" md={2}>First Name</Label>
<Col md={10}>
<Control.text model=".firstname" id="firstname" name="firstname"
placeholder="First Name"
className="form-control"
validators={{
required, minLength: minLength(3), maxLength: maxLength(15)
}}
/>
<Errors
className="text-danger"
model=".firstname"
show="touched"
messages={{
required: "Required",
minLength: 'Must be greater that 2 characters',
maxLength: 'Must be 15 characters or less',
}}
></Errors>
</Col>
</Row>
{/* lastname */}
<Row className="form-group">
<Label htmlFor="lastname" md={2}>Last Name</Label>
<Col md={10}>
<Control.text model=".lastname" id="lastname" name="lastname"
placeholder="Last Name"
className="form-control"
validators={{
required, minLength: minLength(3), maxLength: maxLength(15)
}}
/>
<Errors
className="text-danger"
model=".lastname"
show="touched"
messages={{
required: 'Required',
minLength: 'Must be greater than 2 characters',
maxLength: 'Must be 15 characters or less'
}}
/>
</Col>
</Row>
{/* telphone */}
<Row className="form-group">
<Label htmlFor="telnum" md={2}>Contact Tel.</Label>
<Col md={10}>
<Control.text model=".telnum" id="telnum" name="telnum"
placeholder="Tel. Number"
className="form-control"
validators={{
required, minLength: minLength(3), maxLength: maxLength(15), isNumber
}}
/>
<Errors
className="text-danger"
model=".telnum"
show="touched"
messages={{
required: 'Required',
minLength: 'Must be greater than 2 numbers',
maxLength: 'Must be 15 numbers or less',
isNumber: 'Must be a number'
}}
/>
</Col>
</Row>
{/* email */}
<Row className="form-group">
<Label htmlFor="email" md={2}>Email</Label>
<Col md={10}>
<Control.text model=".email" id="email" name="email"
placeholder="Email"
className="form-control"
validators={{
required, validEmail
}}
/>
<Errors
className="text-danger"
model=".email"
show="touched"
messages={{
required: 'Required',
validEmail: 'Invalid Email Address'
}}
/>
</Col>
</Row>
{/* ? */}
<Row className="form-group">
<Col md={{size: 6, offset: 2}}>
<div className="form-check">
<Label check>
<Control.checkbox model=".agree" name="agree"
className="form-check-input"
/>{' '}
<strong>May we contact you? </strong>
</Label>
</div>
</Col>
<Col md={{ size: 3, offset: 1 }}>
<Control.select model=".contactType"
className="form-control"
name="contactType"
>
<option>Tel.</option>
<option>Email</option>
</Control.select>
</Col>
</Row>
{/* feedback */}
<Row className="form-group">
<Label htmlFor="message" md={2}>Your Feedback</Label>
<Col md={10}>
<Control.textarea model=".message" id="message" name="message"
rows="12"
className="form-control"
/>
</Col>
</Row>
{/* submit button */}
<Row className="form-group">
<Col md={{size: 10, offset: 2}}>
<Button type="submit" color="primary">
Send Feedback
</Button>
</Col>
</Row>
</Form>
</div>
</div>
</div>
);
}
}
export default Contact;
Home Component
import React from "react";
import {
Card,
CardImg,
CardText,
CardBody,
CardTitle,
CardSubtitle
} from "reactstrap";
import { Loading } from "./LoadingComponent";
import { baseUrl } from "../shared/baseUrl";
import { FadeTransform } from 'react-animation-components';
function RenderCard({item, isLoading, errMess }) {
if (isLoading) {
return (
<Loading />
);
}
else if ( errMess ) {
return (
<h4>{errMess}</h4>
);
}
else{
return(
<FadeTransform
in
transformProps={{
exitTransform: 'scale(0.5) translateY(-50%)'
}}>
<Card>
<CardImg width="100%" src={baseUrl + item.image} alt={item.name} />
<CardBody>
<CardTitle> {item.name} </CardTitle>
{item.designation ? <CardSubtitle>{item.designation}</CardSubtitle> : null }
<CardText>{item.description}</CardText>
</CardBody>
</Card>
</FadeTransform>
);
}
}
function Home(props) {
return(
<div className="container">
<div className="row align-items-start">
<div className="col-12 col-md m-1">
<RenderCard
item={props.dish}
isLoading={props.dishesLoading}
errMess={props.dishErrMess}
/>
</div>
<div className="col-12 col-md m-1">
<RenderCard
item={props.promotion}
isLoading={props.promoLoading}
errMess={props.promoErrMess}
/>
</div>
<div className="col-12 col-md m-1">
<RenderCard
item={props.leader}
isLoading={props.leaderLoading}
errMess={props.leaderErrMess}
/>
</div>
</div>
</div>
);
}
export default Home;
Leaders
import * as ActionTypes from './ActionTypes';
// import { LEADERS } from '../shared/leaders';
/*reducer functions
takes two params
* action
# payloads of information that send data from your application
to the store.
# type property (indicates type of action to be performed)
# payload (data necessary for the action)
* state
## action typically handled through a switch statement switching
on the action type.
## return the previous state in the default case
* */
////reducer function, returns the next immutable state
export const Leaders = (state = {
isLoading: true,
errMess: null,
leaders: []
}, action) => {
switch (action.type) {
case ActionTypes.ADD_LEADERS:
return { ...state, isLoading: false, errMess: null, leaders: action.payload };
case ActionTypes.LEADERS_LOADING:
return { ...state, isLoading: true, errMess: null, leaders: [] }
case ActionTypes.LEADERS_FAILED:
return { ...state, isLoading: false, errMess: action.payload };
default:
return state;
}
}
Main Component
import React, { Component } from 'react';
import Home from './HomeComponent';
import Menu from './MenuComponent';
import About from './AboutComponent';
import Contact from './ContactComponent';
import Header from './HeaderComponent';
import Footer from './FooterComponent';
import DishDetail from './DishdetailComponent';
import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
import {connect} from 'react-redux';
import { postComment, postFeedback, fetchDishes, fetchComments, fetchPromos, fetchLeaders } from '../redux/ActionCreators';
import { actions } from 'react-redux-form';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
const mapStateToProps = state => {
return{
comments: state.comments,
dishes: state.dishes,
leaders: state.leaders,
promotions: state.promotions,
}
}
const mapDispatchToProps = (dispatch) => ({
postComment: (dishId, rating, author, comment) => dispatch(postComment(dishId, rating, author, comment)),
fetchDishes: () => { dispatch(fetchDishes()) },
resetFeedbackForm: () => { dispatch(actions.reset('feedback')) },
fetchComments: () => dispatch(fetchComments()),
fetchPromos: () => dispatch(fetchPromos()),
fetchLeaders: () => dispatch(fetchLeaders()),
postFeedback: (firstname, lastname, telnum, email, agree, contactType, message) => dispatch(postFeedback(firstname, lastname, telnum, email, agree, contactType, message)),
});
class Main extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.props.fetchDishes();
this.props.fetchComments();
this.props.fetchPromos();
this.props.fetchLeaders();
}
render() {
const HomePage = () => {
return (
<Home
dish={
this.props.dishes.dishes.filter(dish => dish.featured)[0]
}
dishesLoading={this.props.dishes.isLoading}
dishErrMess={this.props.dishes.errMess}
promotion={
this.props.promotions.promotions.filter(promo => promo.featured)[0]
}
promoLoading={this.props.promotions.isLoading}
promoErrMess={this.props.promotions.errMess}
leader={
this.props.leaders.leaders.filter((leader) => leader.featured)[0]
}
leaderLoading={this.props.leaders.isLoading}
leaderErrMess={this.props.leaders.errMess}
/>
);
};
const AboutUsPage = () => {
return(
<About
leaders={this.props.leaders.leaders}
leaderLoading={this.props.leaders.isLoading}
leaderErrMess={this.props.leaders.errMess}
/>
);
};
const DishWithId = ({match}) => {
return(
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish.id === parseInt(match.params.dishId, 10))[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dishId === parseInt(match.params.dishId, 10))}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
/>
);
};
return (
<div>
<Header></Header>
<TransitionGroup>
<CSSTransition key={this.props.location.key} classNames="page" timeout={300}>
<Switch>
<Route path="/home" component={ HomePage } />
<Route exact path="/menu"
component={() => <Menu dishes={this.props.dishes}/> }
/>
<Route path="/menu/:dishId" component={DishWithId} />
<Route exact path="/contactus" component={() => <Contact
resetFeedbackForm={this.props.resetFeedbackForm }
postFeedback={this.props.postFeedback}
/> }
/>
<Route exact path="/aboutus" component={ AboutUsPage } />
{/* if url dosesnt match, bydefault redirect to */}
<Redirect to="/home" />
</Switch>
</CSSTransition>
</TransitionGroup>
<Footer></Footer>
</div>
);
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
/**
*
* - connect(): generates a wrapper container component that
* subscribe to the store.
*/
For Reference .js File is attached below