Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Product page with pagination support #73

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const withCss = require('@zeit/next-css');
const path = require('path');

const withOffline = require('next-offline');
const withSass = require('@zeit/next-sass');

Expand Down Expand Up @@ -29,7 +27,7 @@ module.exports = withOffline(
backend_hostname,
'https://via.placeholder.com'
],
},
}
})
)
);
22,872 changes: 12,005 additions & 10,867 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@
"prettier": "^2.2.1",
"serialize-javascript": "^2.1.2"
}
}
}
71 changes: 12 additions & 59 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,25 @@
import Layout from '../src/components/layouts/Layout';
import Link from 'next/link';
import client from '../src/apollo/ApolloClient';
import AddToCartButton from '../src/components/cart/AddToCartButton';
import Hero from '../src/components/home/Hero';
import Image from '../src/components/Image';
import { PRODUCTS_QUERY } from '../src/queries';

const NewProducts = ({ products }) => {
return (
<div className="container mt-5">
<h2 className="text-center mb-5">Products</h2>
{products.length ? (
<div className="mt-2">
<div className="products-wrapper row">
{products.map((item) =>
// @TODO Need to add support for Group product.
undefined !== item && 'GroupProduct' !== item.__typename ? (
<div className="product-container col-md-3 mb-5" key={item.id}>
{/* @TODO need to get rid of using databseId here. */}
<Link href={`/product/${item.slug}`}>
<a>
<span className="product-link">
<Image
src={item?.image?.sourceUrl}
alt={item?.image?.altText || item?.name}
/>
<h5 className="product-name">{item.name}</h5>
<p className="product-price">{item.price}</p>
</span>
</a>
</Link>
<AddToCartButton product={item} />
</div>
) : (
''
)
)}
</div>
</div>
) : (
''
)}
</div>
);
};

const Index = (props) => {
const { products } = props;
import { getProductPageStaticProps } from '../src/reusable-static-props';
import Products from '../src/components/products';
import Link from 'next/link';

const Index = ({ products }) => {
return (
<Layout>
<Hero />
{/*<Categories/>*/}
<NewProducts products={products} />
<Products products={products} />

<div className="wd-shop-button">
<Link href={'/products'}>
<a className="btn btn-primary">All Products</a>
</Link>
</div>
</Layout>
);
};

export async function getStaticProps() {
const { data } = await client.query({
query: PRODUCTS_QUERY
});
return {
props: {
products: data.products.nodes
},
revalidate: 1
};
};
export const getStaticProps = getProductPageStaticProps;

export default Index;
17 changes: 5 additions & 12 deletions pages/product/[slug].js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ import Layout from '../../src/components/layouts/Layout';
import AddToCartButton from '../../src/components/cart/AddToCartButton';
import client from '../../src/apollo/ApolloClient';
import Image from '../../src/components/Image';
import {
PRODUCT_QUERY,
PRODUCT_SLUGS
} from '../../src/queries';
import { PRODUCT_QUERY, PRODUCT_SLUGS } from '../../src/queries';
import Gallery from '../../src/components/gallery';

const Product = ({data}) => {

const { product } = data || {}
const Product = ({ data }) => {
const { product } = data || {};

return (
<Layout>
Expand Down Expand Up @@ -39,11 +35,9 @@ const Product = ({data}) => {
dangerouslySetInnerHTML={{ __html: product?.description }}
/>
</div>
<Gallery {...product?.galleryImages}/>
<Gallery {...product?.galleryImages} />
</div>
) : (
null
)}
) : null}
</Layout>
);
};
Expand All @@ -66,7 +60,6 @@ export async function getStaticProps({ params }) {
}

export async function getStaticPaths() {

const { data } = await client.query({
query: PRODUCT_SLUGS
});
Expand Down
26 changes: 26 additions & 0 deletions pages/products/[page].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Products from '../../src/components/products';
import Layout from '../../src/components/layouts/Layout';
import { getProductPageStaticProps } from '../../src/reusable-static-props';

const ProductsPage = ({ products, pageInfo }) => {
return (
<Layout>
<Products paginationInfo={pageInfo} products={products} />
</Layout>
);
};

export const getStaticProps = getProductPageStaticProps;

export async function getStaticPaths() {
return {
paths: [
{
params: { page: '1' }
}
],
fallback: 'blocking'
};
}

export default ProductsPage;
11 changes: 11 additions & 0 deletions pages/products/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Products from '../../src/components/products';
import Layout from '../../src/components/layouts/Layout';
import { getProductPageStaticProps } from '../../src/reusable-static-props';

const ProductsPage = ({ products, pageInfo }) => (
<Layout>
<Products paginationInfo={pageInfo} products={products} />
</Layout>
);
export const getStaticProps = getProductPageStaticProps;
export default ProductsPage;
42 changes: 19 additions & 23 deletions src/components/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,38 @@ import Img from 'next/image';
import PropTypes from 'prop-types';

const Image = (props) => {

const [error, setError] = useState(false);

const {
src,
width,
height,
alt,
...otherProps
} = props;


const { src, width, height, alt, ...otherProps } = props;

// URL to use if the actual image fails to load.
const fallBackUrl = '/static/placeholder.png';

/**
* Handles any error when loading the image.
*
*
* @return {void}
*/
const errorHandler = () => {
setError(true);
}
};

const imageSrc = (error ? false : src) || fallBackUrl;

return (
<Img
src={error ? fallBackUrl : src}
width={width}
height={height}
onError={errorHandler}
alt={alt}
{...otherProps}/>
<Img
src={imageSrc}
width={width}
height={height}
onError={errorHandler}
alt={alt}
{...otherProps}
/>
);
};

Image.propTypes = {
src: PropTypes.string.isRequired,
src: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
alt: PropTypes.string,
Expand All @@ -50,11 +46,11 @@ Image.propTypes = {
objectPosition: PropTypes.string,
className: PropTypes.string,
id: PropTypes.string
}
};

Image.defaultProps = {
width: 240,
height: 240,
}
height: 240
};

export default Image;
80 changes: 80 additions & 0 deletions src/components/pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useRouter } from 'next/router';
import Link from 'next/link';
import PropTypes from 'prop-types';

export default function Pagination({ paginationInfo }) {
const router = useRouter();

if (!paginationInfo?.total || paginationInfo.total <= paginationInfo.perPage) {
return null;
}

let currentPage = parseInt(router?.query?.page);
if (isNaN(currentPage) || !currentPage) {
currentPage = 1;
}

function getPagesCount() {
const { perPage, total } = paginationInfo;
let pagesCount = Math.floor(total / perPage);
if (total % perPage) {
pagesCount++;
}
return pagesCount;
}

const pagesCount = getPagesCount();

if (pagesCount < 2) {
return null;
}

const pagesArray = Array(pagesCount).fill(0);

return (
<ul className="wd-pagination">
{currentPage > 1 && (
<li>
<Link href={`/products/${currentPage - 1}`}>
<a>Prev</a>
</Link>
</li>
)}

{pagesArray.map((_, index) => {
const page = index + 1;
return (
<li key={page}>
{page === currentPage ? (
<span>{page}</span>
) : (
<Link href={`/products/${page}`}>
<a>{page}</a>
</Link>
)}
</li>
);
})}

{currentPage < pagesArray.length && (
<li>
<Link href={`/products/${currentPage + 1}`}>
<a>Next</a>
</Link>
</li>
)}
</ul>
);
}

Pagination.propTypes = {
paginationInfo: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.shape({
perPage: PropTypes.number,
hasMore: PropTypes.bool,
hasPrevious: PropTypes.bool,
total: PropTypes.number
})
])
};
27 changes: 27 additions & 0 deletions src/components/product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from 'next/link';
import Image from './Image';
import AddToCartButton from './cart/AddToCartButton';
import PropTypes from 'prop-types';

const Product = ({ item }) => {
return (
<div className="product-container col-md-3 mb-5">
{/* @TODO need to get rid of using databseId here. */}
<Link href={`/product/${item.slug}`}>
<a>
<span className="product-link">
<Image
src={item?.image?.sourceUrl}
alt={item?.image?.altText || item?.name}
/>
<h5 className="product-name">{item.name}</h5>
<p className="product-price">{item.price}</p>
</span>
</a>
</Link>
<AddToCartButton product={item} />
</div>
);
};

export default Product;
Loading