diff --git a/src/components/HomePage/index.tsx b/src/components/HomePage/index.tsx
index 1c8e49b9fc..4d6747acb2 100644
--- a/src/components/HomePage/index.tsx
+++ b/src/components/HomePage/index.tsx
@@ -2,20 +2,22 @@ import * as React from "react";
import { Query } from "react-apollo";
import { Link } from "react-router-dom";
-import { Button, Carousel, Loader, ProductListItem } from "..";
+import {
+ Button,
+ Carousel,
+ Loader,
+ ProductListItem,
+ ProductsFeatured
+} from "..";
import { ProductsList } from "../../core/types/saleor";
-import { generateCategoryUrl, generateProductUrl } from "../../core/utils";
+import { generateCategoryUrl, maybe } from "../../core/utils";
import { Error } from "../Error";
import { GET_PRODUCTS_AND_CATEGORIES } from "./queries";
import "./scss/index.scss";
const canDisplay = (data: ProductsList) =>
- data &&
- data.shop &&
- data.shop.homepageCollection &&
- data.categories &&
- data.categories.edges;
+ maybe(() => !!data.shop.homepageCollection && !!data.categories.edges, false);
const HomePage: React.SFC = () => (
@@ -26,17 +28,14 @@ const HomePage: React.SFC = () => (
>
{({ error, data, loading }) => {
if (canDisplay(data)) {
+ const { backgroundImg } = data.shop.homepageCollection;
return (
<>
@@ -67,23 +66,7 @@ const HomePage: React.SFC = () => (
)}
-
-
-
Featured
-
- {data.shop.homepageCollection.products.edges.map(
- ({ node: product }) => (
-
-
-
- )
- )}
-
-
-
+
Shop by category
diff --git a/src/components/HomePage/queries.ts b/src/components/HomePage/queries.ts
index fadef6f7dc..89d8460296 100644
--- a/src/components/HomePage/queries.ts
+++ b/src/components/HomePage/queries.ts
@@ -9,25 +9,6 @@ export const GET_PRODUCTS_AND_CATEGORIES = gql`
url
}
name
- products(first: 20) {
- edges {
- node {
- id
- name
- thumbnailUrl
- thumbnailUrl2x: thumbnailUrl(size: 510)
- category {
- id
- name
- }
- price {
- currency
- amount
- localized
- }
- }
- }
- }
}
}
categories(level: 0, first: 4) {
diff --git a/src/components/HomePage/scss/index.scss b/src/components/HomePage/scss/index.scss
index 084351d8b2..f0cb279884 100644
--- a/src/components/HomePage/scss/index.scss
+++ b/src/components/HomePage/scss/index.scss
@@ -60,22 +60,6 @@
}
}
- &__featured {
- padding: 2rem 0 4rem;
-
- h3 {
- text-transform: uppercase;
- font-weight: $bold-font-weight;
- margin-bottom: 2rem;
- }
-
- a {
- display: inline-block;
- text-decoration: none;
- color: $base-font-color;
- }
- }
-
&__categories {
margin-bottom: 2rem;
diff --git a/src/components/ProductFilters/scss/index.scss b/src/components/ProductFilters/scss/index.scss
index 85190bc75a..841209031b 100644
--- a/src/components/ProductFilters/scss/index.scss
+++ b/src/components/ProductFilters/scss/index.scss
@@ -15,9 +15,5 @@
@media (max-width: $small-screen) {
grid-template-columns: 1fr;
}
-
- &__filter {
- min-width: 15rem;
- }
}
}
diff --git a/src/components/ProductsFeatured/index.tsx b/src/components/ProductsFeatured/index.tsx
new file mode 100644
index 0000000000..7cd20eb45e
--- /dev/null
+++ b/src/components/ProductsFeatured/index.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import { Query } from "react-apollo";
+import { Link } from "react-router-dom";
+
+import { Carousel, Loader, ProductListItem } from "..";
+import { generateProductUrl, maybe } from "../../core/utils";
+import { GET_FEATURED_PRODUCTS } from "./queries";
+
+import "./scss/index.scss";
+
+interface ProductsFeaturedProps {
+ title?: string;
+}
+
+const ProductsFeatured: React.SFC
= ({ title }) => {
+ return (
+
+ {({ error, data, loading }) => {
+ const products = maybe(
+ () => data.shop.homepageCollection.products.edges,
+ []
+ );
+
+ if (products.length) {
+ return (
+
+
+
{title}
+
+ {products.map(({ node: product }) => (
+
+
+
+ ))}
+
+
+
+ );
+ }
+ if (loading) {
+ return ;
+ }
+ return null;
+ }}
+
+ );
+};
+
+ProductsFeatured.defaultProps = {
+ title: "Featured"
+};
+
+export default ProductsFeatured;
diff --git a/src/components/ProductsFeatured/queries.tsx b/src/components/ProductsFeatured/queries.tsx
new file mode 100644
index 0000000000..b312356bc6
--- /dev/null
+++ b/src/components/ProductsFeatured/queries.tsx
@@ -0,0 +1,30 @@
+import gql from "graphql-tag";
+
+export const GET_FEATURED_PRODUCTS = gql`
+ query ProductsList {
+ shop {
+ homepageCollection {
+ id
+ products(first: 20) {
+ edges {
+ node {
+ id
+ name
+ thumbnailUrl
+ thumbnailUrl2x: thumbnailUrl(size: 510)
+ category {
+ id
+ name
+ }
+ price {
+ currency
+ amount
+ localized
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/src/components/ProductsFeatured/scss/index.scss b/src/components/ProductsFeatured/scss/index.scss
new file mode 100644
index 0000000000..019811b0c1
--- /dev/null
+++ b/src/components/ProductsFeatured/scss/index.scss
@@ -0,0 +1,17 @@
+@import "../../App/scss/variables.scss";
+
+.products-featured {
+ padding: 2rem 0 4rem;
+
+ h3 {
+ text-transform: uppercase;
+ font-weight: $bold-font-weight;
+ margin-bottom: $spacer * 2;
+ }
+
+ a {
+ display: inline-block;
+ text-decoration: none;
+ color: $base-font-color;
+ }
+}
diff --git a/src/components/ProductsList/index.tsx b/src/components/ProductsList/index.tsx
index 6c4e4c7d8b..9132a7b816 100644
--- a/src/components/ProductsList/index.tsx
+++ b/src/components/ProductsList/index.tsx
@@ -16,6 +16,7 @@ interface ProductsListProps {
onLoadMore: () => void;
products: CategoryProductInterface;
onOrder: (order: string) => void;
+ notFoundPhrase?: string;
}
export const ProductList: React.SFC = ({
@@ -24,7 +25,8 @@ export const ProductList: React.SFC = ({
hasNextPage,
onLoadMore,
products,
- onOrder
+ onOrder,
+ notFoundPhrase
}) => {
const filterOptions = [
{ value: "price", label: "Price Low-High" },
@@ -32,6 +34,10 @@ export const ProductList: React.SFC = ({
{ value: "name", label: "Name Increasing" },
{ value: "-name", label: "Name Decreasing" }
];
+ const sortValues = filterOptions.find(
+ option => option.value === filters.sortBy
+ );
+ const hasProducts = !!products.totalCount;
return (
@@ -45,19 +51,20 @@ export const ProductList: React.SFC
= ({
)}
- Sort by:{" "}
- option.value === filters.sortBy) ||
- ""
- }
- isSearchable={false}
- onChange={event => onOrder(event.value)}
- />
+ {hasProducts && (
+ <>
+ Sort by:{" "}
+ onOrder(event.value)}
+ />
+ >
+ )}
- {products.edges.length > 0 ? (
+ {hasProducts ? (
<>
{products.edges.map(({ node: product }) => (
@@ -83,7 +90,7 @@ export const ProductList: React.SFC
= ({
>
) : (
- We couldn't find any product matching these conditions
+ {notFoundPhrase}
)}
@@ -91,4 +98,8 @@ export const ProductList: React.SFC = ({
);
};
+ProductList.defaultProps = {
+ notFoundPhrase: "We couldn't find any product matching these conditions"
+};
+
export default ProductList;
diff --git a/src/components/index.ts b/src/components/index.ts
index 707617702a..837f233c03 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -37,3 +37,4 @@ export { default as CheckoutPayment } from "./CheckoutPayment";
export { default as ShippingAddressForm } from "./ShippingAddressForm";
export { default as CheckoutReview } from "./CheckoutReview";
export { default as Debounce } from "./Debounce";
+export { default as ProductsFeatured } from "./ProductsFeatured";
diff --git a/src/core/utils.ts b/src/core/utils.ts
index cfe538fcc2..926242c751 100644
--- a/src/core/utils.ts
+++ b/src/core/utils.ts
@@ -101,3 +101,12 @@ export const convertSortByFromString = (sortBy: string) => {
: undefined;
return { field, direction };
};
+
+export function maybe(exp: () => T, d?: T) {
+ try {
+ const result = exp();
+ return result === undefined ? d : result;
+ } catch {
+ return d;
+ }
+}
diff --git a/src/views/Category/CategoryPage.tsx b/src/views/Category/CategoryPage.tsx
index 0f115823ee..db125d48a4 100644
--- a/src/views/Category/CategoryPage.tsx
+++ b/src/views/Category/CategoryPage.tsx
@@ -2,14 +2,14 @@ import "./scss/index.scss";
import * as React from "react";
-import { Breadcrumbs, ProductsList } from "../../components";
+import { Breadcrumbs, ProductsFeatured, ProductsList } from "../../components";
import { Filters, ProductFilters } from "../../components/ProductFilters";
import {
Category_attributes_edges_node,
Category_category,
Category_products
} from "../../core/types/saleor";
-import { getDBIdFromGraphqlId, slugify } from "../../core/utils";
+import { getDBIdFromGraphqlId, maybe, slugify } from "../../core/utils";
interface CategoryPageProps {
attributes: Category_attributes_edges_node[];
@@ -61,19 +61,18 @@ export const CategoryPage: React.SFC = ({
onPriceChange,
onOrder
}) => {
- const canDisplayProducts =
- products &&
- products.edges !== undefined &&
- products.totalCount !== undefined;
+ const canDisplayProducts = maybe(
+ () => products.edges && products.totalCount !== undefined,
+ false
+ );
+ const hasProducts = canDisplayProducts && !!products.totalCount;
return (
@@ -84,12 +83,14 @@ export const CategoryPage: React.SFC
= ({
-
+ {hasProducts && (
+
+ )}
{canDisplayProducts && (
= ({
products={products}
/>
)}
+ {!hasProducts && }
);
};
diff --git a/src/views/Category/index.tsx b/src/views/Category/index.tsx
index 2f16adf1be..be799d5195 100644
--- a/src/views/Category/index.tsx
+++ b/src/views/Category/index.tsx
@@ -8,18 +8,15 @@ import { Error } from "../../components/Error";
import NetworkStatus from "../../components/NetworkStatus";
import { NotFound } from "../../components/NotFound";
import { OfflinePlaceholder } from "../../components/OfflinePlaceholder";
-import {
- AttributeList,
- Filters,
- ProductFilters
-} from "../../components/ProductFilters";
+import { AttributeList, Filters } from "../../components/ProductFilters";
import { PRODUCTS_PER_PAGE } from "../../core/config";
import { Category } from "../../core/types/saleor";
import {
convertSortByFromString,
convertToAttributeScalar,
getAttributesFromQs,
- getGraphqlIdFromDBId
+ getGraphqlIdFromDBId,
+ maybe
} from "../../core/utils";
import { CategoryPage } from "./CategoryPage";
import { GET_CATEGORY_AND_ATTRIBUTES } from "./queries";
@@ -57,13 +54,10 @@ export const CategoryView: React.SFC
= ({
errorPolicy="all"
>
{({ loading, error, data, fetchMore }) => {
- const canDisplayFilters =
- data &&
- data.attributes &&
- data.attributes.edges !== undefined &&
- data.category &&
- data.category.name !== undefined;
-
+ const canDisplayFilters = maybe(
+ () => data.attributes.edges && data.category.name,
+ false
+ );
if (canDisplayFilters) {
const handleLoadMore = () =>
fetchMore({
@@ -97,9 +91,10 @@ export const CategoryView: React.SFC = ({
attributes={data.attributes.edges.map(edge => edge.node)}
category={data.category}
displayLoader={loading}
- hasNextPage={
- data.products && data.products.pageInfo.hasNextPage
- }
+ hasNextPage={maybe(
+ () => data.products.pageInfo.hasNextPage,
+ false
+ )}
filters={filters}
products={data.products}
onAttributeFiltersChange={(attribute, values) => {
diff --git a/src/views/Search/index.tsx b/src/views/Search/index.tsx
index efe124980f..72729906d2 100644
--- a/src/views/Search/index.tsx
+++ b/src/views/Search/index.tsx
@@ -5,7 +5,12 @@ import * as React from "react";
import { Query } from "react-apollo";
import { RouteComponentProps } from "react-router";
-import { Debounce, Loader, ProductsList } from "../../components";
+import {
+ Debounce,
+ Loader,
+ ProductsFeatured,
+ ProductsList
+} from "../../components";
import { Error } from "../../components/Error";
import NetworkStatus from "../../components/NetworkStatus";
import { OfflinePlaceholder } from "../../components/OfflinePlaceholder";
@@ -19,7 +24,8 @@ import { SearchResults } from "../../core/types/saleor";
import {
convertSortByFromString,
convertToAttributeScalar,
- getAttributesFromQs
+ getAttributesFromQs,
+ maybe
} from "../../core/utils";
import { GET_SEARCH_PRODUCTS } from "./queries";
import SearchPage from "./SearchPage";
@@ -55,14 +61,12 @@ export const SearchView: React.SFC = ({
errorPolicy="all"
>
{({ error, data, loading, fetchMore }) => {
- const canDisplayProducts =
- data &&
- data.attributes &&
- data.attributes.edges !== undefined &&
- data.products &&
- data.products.edges !== undefined &&
- data.products.totalCount !== undefined;
-
+ const canDisplayFilters = maybe(() => data.attributes.edges, false);
+ const canDisplayProducts = maybe(
+ () =>
+ data.products.totalCount !== undefined && data.products.edges,
+ false
+ );
const handleQueryChange = (
event: React.ChangeEvent
) => {
@@ -98,54 +102,57 @@ export const SearchView: React.SFC = ({
}
});
- const canDisplayFilters =
- data && data.attributes && data.attributes.edges !== undefined;
-
return (
- {({ change, value: query }) => (
-
- {canDisplayFilters ? (
- <>
+ {({ change, value: query }) => {
+ if (loading) {
+ return ;
+ }
+
+ const hasProducts =
+ canDisplayProducts && !!data.products.totalCount;
+ const updateQueryString = (key: string, value?) => {
+ qs[key] = value || key;
+ history.replace("?" + stringifyQs(qs));
+ };
+
+ if (!!error) {
+ return isOnline ? (
+
+ ) : (
+
+ );
+ }
+
+ return (
+
+ {hasProducts && canDisplayFilters && (
edge.node
)}
filters={filters}
- onAttributeFiltersChange={(attribute, values) => {
- qs[attribute] = values;
- history.replace("?" + stringifyQs(qs));
- }}
- onPriceChange={(field, value) => {
- qs[field] = value;
- history.replace("?" + stringifyQs(qs));
- }}
+ onAttributeFiltersChange={updateQueryString}
+ onPriceChange={updateQueryString}
/>
- {canDisplayProducts && (
- {
- qs.sortBy = sortBy;
- history.replace("?" + stringifyQs(qs));
- }}
- />
- )}
- >
- ) : !!error ? (
- isOnline ? (
-
- ) : (
-
- )
- ) : (
-
- )}
-
- )}
+ )}
+ {canDisplayProducts && (
+
+ )}
+ {!hasProducts && (
+
+ )}
+
+ );
+ }}
);
}}
diff --git a/webpack.config.js b/webpack.config.js
index 0e0a59b7ce..6f79048a85 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -35,7 +35,10 @@ module.exports = (env, argv) => {
test: /\.(scss|css)$/,
use: [
devMode ? "style-loader" : MiniCssExtractPlugin.loader,
- { loader: "css-loader" },
+ {
+ loader: "css-loader",
+ options: { sourceMap: true }
+ },
{ loader: "sass-loader" }
]
},