Implementing PDP page

parent d0865944
.appFooter { .appFooter {
position: fixed; // position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
} }
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
left: 0; left: 0;
width: 100%; width: 100%;
box-shadow: 0 0 2px 0 rgba-background($primary-black, .24), 0 2px 2px 0 rgba-background($primary-black, .12); box-shadow: 0 0 2px 0 rgba-background($primary-black, .24), 0 2px 2px 0 rgba-background($primary-black, .12);
background: $primary-white;
@include flex-box(); @include flex-box();
h1 { h1 {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
height: 100%; height: 100%;
.loader { .loader {
border: 16px solid $gray; border: 16px solid $gray1;
border-radius: 50%; border-radius: 50%;
border-top: 16px solid $primary; border-top: 16px solid $primary;
width: 120px; width: 120px;
......
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import api from 'services/api';
import App from 'App'; import App from 'App';
import configureStore from 'redux/configureStore'; import configureStore from 'redux/configureStore';
...@@ -19,6 +20,8 @@ export function main(args = {}) { ...@@ -19,6 +20,8 @@ export function main(args = {}) {
if (renderNow) { if (renderNow) {
api.init(store);
reactDOM.render( reactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
......
class Image {
constructor(data) {
this.data = data || {};
}
get width() {
const { width } = this.data;
return width;
}
get height() {
const { height } = this.data;
return height;
}
get alt() {
const { alt } = this.data;
return alt || 'product image';
}
get url() {
const { href: url } = this.data;
return url;
}
}
export default Image;
\ No newline at end of file
import Product from 'models/product';
import Image from 'models/image';
import ROUTES from 'constants/routes';
class ListingProduct extends Product {
constructor(data) {
super(data);
this._image = new Image(this.data.thumbnail);
}
get image() {
return this._image;
}
get sellingPrice() {
const { high, low } = this.data.priceRange.selling;
return `$${low} - $${high}`;
}
get detailsPageUrl() {
const { id } = this.data;
return ROUTES.details.getUrl(id);
}
}
export default ListingProduct;
\ No newline at end of file
import Image from 'models/image';
class Product {
constructor(data) {
this.data = data || {};
this._image = new Image(this.data.hero);
}
get name() {
const { name = '' } = this.data;
return name.replace(/&amp;/, '&');
}
get image() {
return this._image;
}
get sellingPrice() {
const { priceRange: { selling: { high, low } = {} } = {} } = this.data;
return `$${low} - $${high}`;
}
}
export default Product;
\ No newline at end of file
import Product from 'models/product';
import Image from 'models/image';
class ProductDetails extends Product {
constructor(data) {
super(data);
const { images = [] } = this.data;
this._images = images.map(item => new Image(item));
}
get images() {
return this._images;
}
}
export default ProductDetails;
\ No newline at end of file
import React, { Component } from 'react'; import React, { Component } from 'react';
import styles from './PDP.module.scss'; import style from './PDP.module.scss';
import Product from 'models/product';
class PDPPage extends Component { class PDPPage extends Component {
componentDidMount() {
const { match: { params: { productId } } } = this.props;
this.props.getProductDetails(productId);
}
render() { render() {
const image = { url: 'https://www.westelm.com/weimgs/rk/images/wcm/products/202003/0005/belgian-linen-ladder-stripe-embroidery-duvet-cover-shams-s-m.jpg' };
const { product } = this.props;
const { name, } = new Product(product);
return ( return (
<section className={styles.PDPPage}> <section className={style.PDPPage}>
PDP Page! <div className={style.content}>
<div className={style.images}>
<img
src={image.url}
alt={image.alt}
className={style.productImage}
/>
</div>
<div className={style.details}>
<h1>{name}</h1>
<div>Price: 44 - 329</div>
</div>
</div>
</section> </section>
); );
} }
} }
export default PDPPage; export default PDPPage;
\ No newline at end of file
...@@ -2,4 +2,24 @@ ...@@ -2,4 +2,24 @@
.PDPPage { .PDPPage {
margin-top: $large-footer-height; margin-top: $large-footer-height;
h1 {
color: $gray2;
}
.content {
display: flex;
align-items: flex-start;
justify-content: space-evenly;
box-sizing: border-box;
padding: 15px;
flex-wrap: wrap;
.images {}
.details {
// width: 50%;
}
}
} }
\ No newline at end of file
import { connect } from 'react-redux';
import { getProductDetails } from 'redux/actions/product';
import PDPPage from './PDP'; import PDPPage from './PDP';
export default PDPPage; const mapStateToProps = (state) => {
\ No newline at end of file const { product, loading, error } = state.product;
return {
product,
loading,
error
}
};
const mapDispatchToProps = {
getProductDetails
};
export default connect(mapStateToProps, mapDispatchToProps)(PDPPage);
\ No newline at end of file
import React, { Component } from 'react'; import React, { Component } from 'react';
import styles from './PLP.module.scss'; import style from './PLP.module.scss';
import ProductItem from './ProductItem';
import ListingProduct from 'models/listingProduct';
class PLPPage extends Component { class PLPPage extends Component {
render() { render() {
const { name, items } = this.props;
return ( return (
<section className={styles.PLPPage}> <section className={style.PLPPage}>
PLP Page! <h1>{name}</h1>
<ul className={style.products}>
{
items.map(item => {
return (
<ProductItem
key={item.id}
data={new ListingProduct(item)}
/>
)
})
}
</ul>
</section> </section>
); );
} }
......
@import '~styles/variables'; @import '~styles/variables';
@import '~styles/mixins';
.PLPPage { .PLPPage {
margin-top: $large-footer-height; margin-top: $large-footer-height;
padding: 10px;
h1 {
margin: 0;
margin-left: 10px;
color: $gray2;
}
.products {
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
} }
\ No newline at end of file
import React, { Component } from 'react';
import style from './ProductItem.module.scss';
class ProductItem extends Component {
render() {
const { data = {} } = this.props;
const { name, image, sellingPrice, detailsPageUrl } = data;
return (
<li className={style.productItem}>
<a href={detailsPageUrl}>
<img
src={image.url}
alt={image.alt}
className={style.productImage}
/>
</a>
<div className={style.details}>
<a className={style.title} href={detailsPageUrl}>{name}</a>
<div className={style.sellingPrice}>
{sellingPrice}
</div>
</div>
</li>
);
}
}
export default ProductItem;
@import '~styles/variables';
.productItem {
list-style: none;
max-height: 420px;
max-width: 350px;
box-sizing: border-box;
padding: 10px;
.productImage {
width: 100%;
}
.details {
padding: 5px;
}
.sellingPrice {
color: $primary;
font-weight: bold;
margin-top: 5px;
}
}
\ No newline at end of file
import ProductItem from './ProductItem';
export default ProductItem;
\ No newline at end of file
import { connect } from 'react-redux';
import PLPPage from './PLP'; import PLPPage from './PLP';
export default PLPPage; const mapStateToProps = ({ category }) => {
\ No newline at end of file const { items, name } = category;
return {
items,
name
}
};
export default connect(mapStateToProps, null)(PLPPage);
import API from 'services/api';
import { PRODUCT_ACTION_TYPE } from 'redux/constants/actionTypes';
export function getProductDetails(id) {
const { PRODUCT_INIT_BEGIN, PRODUCT_INIT_SUCCESS, PRODUCT_INIT_FAILED } = PRODUCT_ACTION_TYPE;
return async dispatch => {
dispatch({ type: PRODUCT_INIT_BEGIN });
try {
const product = await API.getProductDetails(id);
dispatch({ type: PRODUCT_INIT_SUCCESS, payload: product });
}
catch (err) {
console.log(err);
dispatch({ type: PRODUCT_INIT_FAILED, payload: err });
}
};
}
\ No newline at end of file
...@@ -8,3 +8,13 @@ export const CATEGORY_ACTION_TYPE = Object.freeze({ ...@@ -8,3 +8,13 @@ export const CATEGORY_ACTION_TYPE = Object.freeze({
CATEGORY_INIT_SUCCESS, CATEGORY_INIT_SUCCESS,
CATEGORY_INIT_FAILED, CATEGORY_INIT_FAILED,
}); });
const PRODUCT_INIT_BEGIN = 'PRODUCT_INIT_BEGIN';
const PRODUCT_INIT_SUCCESS = 'PRODUCT_INIT_SUCCESS';
const PRODUCT_INIT_FAILED = 'PRODUCT_INIT_FAILED';
export const PRODUCT_ACTION_TYPE = Object.freeze({
PRODUCT_INIT_BEGIN,
PRODUCT_INIT_SUCCESS,
PRODUCT_INIT_FAILED,
});
\ No newline at end of file
...@@ -21,7 +21,8 @@ export default function categoryReducer(state = initialState, action) { ...@@ -21,7 +21,8 @@ export default function categoryReducer(state = initialState, action) {
case CATEGORY_INIT_SUCCESS: { case CATEGORY_INIT_SUCCESS: {
return { return {
...state, ...state,
loading: false loading: false,
...action.payload
} }
} }
case CATEGORY_INIT_FAILURE: { case CATEGORY_INIT_FAILURE: {
......
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import categoryReducer from './category'; import categoryReducer from './category';
import productReducer from './product';
const rootReducer = combineReducers({ const rootReducer = combineReducers({
category: categoryReducer, category: categoryReducer,
product: productReducer
}); });
export default rootReducer; export default rootReducer;
import { PRODUCT_ACTION_TYPE } from 'redux/constants/actionTypes';
const { PRODUCT_INIT_BEGIN, PRODUCT_INIT_SUCCESS, PRODUCT_INIT_FAILURE } = PRODUCT_ACTION_TYPE;
const initialState = {
loading: true,
error: null,
product: null
};
export default function productReducer(state = initialState, action) {
switch (action.type) {
case PRODUCT_INIT_BEGIN: {
return {
...state,
loading: true
}
}
case PRODUCT_INIT_SUCCESS: {
return {
...state,
loading: false,
product: action.payload
}
}
case PRODUCT_INIT_FAILURE: {
return {
...state,
loading: false,
error: action.payload,
}
}
default:
return state;
}
}
\ No newline at end of file
...@@ -16,15 +16,23 @@ async function get(url) { ...@@ -16,15 +16,23 @@ async function get(url) {
StatusText: ${response.statusText}`; StatusText: ${response.statusText}`;
throw Error(message); throw Error(message);
} }
} }
class ApiClient { class ApiClient {
init(store) {
this.store = store;
}
async getCategoryData() { async getCategoryData() {
return get(URL.CATEGORY); return get(URL.CATEGORY);
} }
async getProductDetails(id) {
const { category: { items = [] } = {} } = this.store.getState();
const product = items.find(item => item.id === id);
return Promise.resolve(product)
}
} }
export default new ApiClient(); export default new ApiClient();
\ No newline at end of file
// Primary // Primary
$primary : #866347; $primary : #866347;
$primary-black : #333333; $primary-black : #333333;
$gray : #F5F6F8; $primary-white : white;
$gray1 : #F5F6F8;
$gray2 : gray;
$large-footer-height: 60px; $large-footer-height: 60px;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment