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" } ] },