Changes from `add/marketplace-product-card-component` after rebasing on feature branch.
This commit is contained in:
parent
85b4011c13
commit
b56654867a
|
@ -25,7 +25,7 @@ export type CategoryAPIItem = {
|
|||
};
|
||||
|
||||
function fetchCategories(): Promise< CategoryAPIItem[] > {
|
||||
return fetch( MARKETPLACE_URL + 'wp-json/wccom-extensions/1.0/categories' )
|
||||
return fetch( MARKETPLACE_URL + '/wp-json/wccom-extensions/1.0/categories' )
|
||||
.then( ( response ) => {
|
||||
if ( ! response.ok ) {
|
||||
throw new Error( response.statusText );
|
||||
|
|
|
@ -1 +1,19 @@
|
|||
// To keep StyleLint happy. Remove when file contains actual code.
|
||||
@import '../../stylesheets/_variables.scss';
|
||||
|
||||
.woocommerce-marketplace {
|
||||
&__discover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 0 $content-spacing-small;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-medium) {
|
||||
.woocommerce-marketplace {
|
||||
&__discover {
|
||||
padding: 0 $content-spacing-large;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductList from '../product-list/product-list';
|
||||
import { fetchDiscoverPageData, ProductGroup } from '../../utils/functions';
|
||||
import './discover.scss';
|
||||
|
||||
export default function Discover(): JSX.Element {
|
||||
export default function Discover(): JSX.Element | null {
|
||||
const [ productGroups, setProductGroups ] = useState<
|
||||
Array< ProductGroup >
|
||||
>( [] );
|
||||
|
||||
useEffect( () => {
|
||||
fetchDiscoverPageData().then( ( products: Array< ProductGroup > ) => {
|
||||
setProductGroups( products );
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
if ( ! productGroups.length ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const groupsList = productGroups.flatMap( ( group ) => group );
|
||||
return (
|
||||
<div className="woocommerce-marketplace__discover">
|
||||
<h1>Discover Our Favorites</h1>
|
||||
{ groupsList.map( ( groups ) => (
|
||||
<ProductList
|
||||
key={ groups.id }
|
||||
title={ groups.title }
|
||||
products={ groups.items }
|
||||
groupURL={ groups.url }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
// To keep StyleLint happy. Remove when file contains actual code.
|
||||
.woocommerce-marketplace {
|
||||
&__extensions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,19 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductList from '../product-list/product-list';
|
||||
import './extensions.scss';
|
||||
import CategorySelector from '../category-selector/category-selector';
|
||||
|
||||
export default function Extensions(): JSX.Element {
|
||||
return (
|
||||
<div className="woocommerce-marketplace__extensions">
|
||||
<ProductList title="Extensions" />
|
||||
<CategorySelector />
|
||||
<div className="woocommerce-marketplace__product-list-content">
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
@import '../../stylesheets/_variables.scss';
|
||||
|
||||
.woocommerce-marketplace {
|
||||
&__product-card {
|
||||
padding: $large-gap;
|
||||
border-radius: $grid-unit-05 !important;
|
||||
margin-top: $large-gap;
|
||||
|
||||
&__content {
|
||||
display: grid;
|
||||
align-items: flex-start;
|
||||
gap: $medium-gap;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
grid-template-rows: auto 1fr 20px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: $grid-unit-60;
|
||||
height: $grid-unit-60;
|
||||
flex-shrink: 0;
|
||||
border-radius: $grid-unit;
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: $medium-gap;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
color: $gray-900;
|
||||
font-size: $editor-font-size;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: $large-gap;
|
||||
margin: -4px 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__vendor {
|
||||
display: flex;
|
||||
gap: $grid-unit-05;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__vendor a {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__price {
|
||||
align-items: flex-end;
|
||||
gap: $grid-unit-05;
|
||||
align-self: stretch;
|
||||
text-decoration: none !important;
|
||||
color: $gray-900 !important;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: $medium-gap;
|
||||
}
|
||||
|
||||
&__price-billing {
|
||||
color: $gray-600;
|
||||
font-size: $default-font-size;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: $medium-gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-medium) {
|
||||
.woocommerce-marketplace {
|
||||
&__product-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Card } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './product-card.scss';
|
||||
import { Product } from '../product-list-content/product-list-content';
|
||||
import { getAdminSetting } from '../../../../client/utils/admin-settings';
|
||||
|
||||
export interface ProductCardProps {
|
||||
type?: string;
|
||||
product: Product;
|
||||
}
|
||||
|
||||
function ProductCard( props: ProductCardProps ): JSX.Element {
|
||||
const { product } = props;
|
||||
const currencySymbol = getAdminSetting( 'currency' )?.symbol;
|
||||
|
||||
let productVendor: string | JSX.Element | null = product?.vendorName;
|
||||
if ( product?.vendorName && product?.vendorUrl ) {
|
||||
productVendor = (
|
||||
<a
|
||||
href={ product.vendorUrl }
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{ product.vendorName }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="woocommerce-marketplace__product-card">
|
||||
<div className="woocommerce-marketplace__product-card__content">
|
||||
<div className="woocommerce-marketplace__product-card__header">
|
||||
<div className="woocommerce-marketplace__product-card__details">
|
||||
{ product.icon && (
|
||||
<img
|
||||
className="woocommerce-marketplace__product-card__icon"
|
||||
src={ product.icon }
|
||||
alt={ product.title }
|
||||
/>
|
||||
) }
|
||||
<div className="woocommerce-marketplace__product-card__meta">
|
||||
<h2 className="woocommerce-marketplace__product-card__title">
|
||||
{ product.title }
|
||||
</h2>
|
||||
{ productVendor && (
|
||||
<p className="woocommerce-marketplace__product-card__vendor">
|
||||
<span>{ __( 'By ', 'woocommerce' ) }</span>
|
||||
{ productVendor }
|
||||
</p>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="woocommerce-marketplace__product-card__description">
|
||||
{ product.description }
|
||||
</p>
|
||||
<Button
|
||||
className="woocommerce-marketplace__product-card__price"
|
||||
href={ product.url }
|
||||
target="_blank"
|
||||
variant="link"
|
||||
>
|
||||
<span>
|
||||
{ product.price === 0 || product.price === '0'
|
||||
? __( 'Free download', 'woocommerce' )
|
||||
: currencySymbol + product.price }
|
||||
</span>
|
||||
<span className="woocommerce-marketplace__product-card__price-billing">
|
||||
{ product.price === 0 || product.price === '0'
|
||||
? ''
|
||||
: __( ' annually', 'woocommerce' ) }
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductCard;
|
|
@ -1,7 +1,6 @@
|
|||
@import '../../stylesheets/_variables.scss';
|
||||
|
||||
.woocommerce-marketplace {
|
||||
|
||||
&__product-list-content {
|
||||
display: grid;
|
||||
gap: $medium-gap;
|
||||
|
@ -32,7 +31,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-xlarge) {
|
||||
@media screen and (min-width: $breakpoint-huge) {
|
||||
.woocommerce-marketplace {
|
||||
&__product-list-content {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
|
|
@ -1,19 +1,49 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './product-list-content.scss';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @woocommerce/dependency-group
|
||||
import ProductCard from '../product-card/product-card';
|
||||
|
||||
export default function ProductListContent(): JSX.Element {
|
||||
export interface Product {
|
||||
id?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
vendorName: string;
|
||||
vendorUrl: string;
|
||||
icon: string;
|
||||
url: string;
|
||||
price: string | number;
|
||||
productType?: string;
|
||||
averageRating?: number | null;
|
||||
reviewsCount?: number | null;
|
||||
currency?: string;
|
||||
}
|
||||
interface ProductListContentProps {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
export default function ProductListContent(
|
||||
props: ProductListContentProps
|
||||
): JSX.Element {
|
||||
const { products } = props;
|
||||
return (
|
||||
<div className="woocommerce-marketplace__product-list-content">
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
<div className="woocommerce-marketplace__extension-card"></div>
|
||||
{ products.map( ( product ) => (
|
||||
<ProductCard
|
||||
key={ product.id }
|
||||
type="classic"
|
||||
product={ {
|
||||
title: product.title,
|
||||
icon: product.icon,
|
||||
vendorName: product.vendorName,
|
||||
vendorUrl: product.vendorUrl,
|
||||
price: product.price,
|
||||
url: product.url,
|
||||
description: product.description,
|
||||
} }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,32 @@
|
|||
.woo-marketplace__product-list-header {
|
||||
color: $gray-900;
|
||||
@import '../../stylesheets/_variables.scss';
|
||||
|
||||
h2 {
|
||||
.woocommerce-marketplace {
|
||||
&__product-list-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: $medium-gap;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
&__product-list-title {
|
||||
flex: 1 0 0;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__product-list-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__product-list-link a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -9,16 +10,25 @@ import './product-list-header.scss';
|
|||
|
||||
interface ProductListHeaderProps {
|
||||
title: string;
|
||||
groupURL: string;
|
||||
}
|
||||
|
||||
export default function ProductListHeader(
|
||||
props: ProductListHeaderProps
|
||||
): JSX.Element {
|
||||
const { title } = props;
|
||||
|
||||
const { title, groupURL } = props;
|
||||
return (
|
||||
<div className="woo-marketplace__product-list-header">
|
||||
<h2>{ title }</h2>
|
||||
<div className="woocommerce-marketplace__product-list-header">
|
||||
<h2 className="woocommerce-marketplace__product-list-title">
|
||||
{ title }
|
||||
</h2>
|
||||
{ groupURL !== null && (
|
||||
<span className="woocommerce-marketplace__product-list-link">
|
||||
<Link href={ groupURL } target="_blank">
|
||||
{ __( 'See more', 'woocommerce' ) }
|
||||
</Link>
|
||||
</span>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,22 +5,24 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CategorySelector from '../category-selector/category-selector';
|
||||
import ProductListContent from '../product-list-content/product-list-content';
|
||||
import ProductListContent, {
|
||||
Product,
|
||||
} from '../product-list-content/product-list-content';
|
||||
import ProductListHeader from '../product-list-header/product-list-header';
|
||||
|
||||
interface ProductListProps {
|
||||
title: string;
|
||||
products: Product[];
|
||||
groupURL: string;
|
||||
}
|
||||
|
||||
export default function ProductList( props: ProductListProps ): JSX.Element {
|
||||
const { title } = props;
|
||||
const { title, products, groupURL } = props;
|
||||
|
||||
return (
|
||||
<div className="woocommerce-marketplace__product-list">
|
||||
<ProductListHeader title={ title } />
|
||||
<CategorySelector />
|
||||
<ProductListContent />
|
||||
<ProductListHeader title={ title } groupURL={ groupURL } />
|
||||
<ProductListContent products={ products } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,14 @@ $content-spacing-small: $grid-unit-20;
|
|||
$content-spacing-large: $grid-unit-40;
|
||||
$content-spacing-small: $medium-gap;
|
||||
$content-spacing-large: $xlarge-gap;
|
||||
$content-spacing-xlarge: $grid-unit-60;
|
||||
$content-max-width: 1600px;
|
||||
|
||||
// Breakpoints
|
||||
$breakpoint-medium: 769px;
|
||||
$breakpoint-large: 1024px;
|
||||
$breakpoint-xlarge: 1500px;
|
||||
$breakpoint-huge: 1920px;
|
||||
|
||||
// Header
|
||||
$header-height-desktop: 89px;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Product } from '../components/product-list-content/product-list-content';
|
||||
import { MARKETPLACE_URL } from '../components/constants';
|
||||
|
||||
interface ProductGroup {
|
||||
id: number;
|
||||
title: string;
|
||||
items: Product[];
|
||||
url: string;
|
||||
}
|
||||
|
||||
// Fetch data for the discover page from the WooCommerce.com API
|
||||
const fetchDiscoverPageData = async (): Promise< Array< ProductGroup > > => {
|
||||
const fetchUrl = MARKETPLACE_URL + '/wp-json/wccom-extensions/2.0/featured';
|
||||
|
||||
return fetch( fetchUrl )
|
||||
.then( ( response ) => {
|
||||
if ( ! response.ok ) {
|
||||
throw new Error( response.statusText );
|
||||
}
|
||||
return response.json();
|
||||
} )
|
||||
.then( ( json ) => {
|
||||
return json;
|
||||
} )
|
||||
.catch( () => {
|
||||
return [];
|
||||
} );
|
||||
};
|
||||
|
||||
export { fetchDiscoverPageData, ProductGroup };
|
|
@ -33,6 +33,9 @@ declare global {
|
|||
cta_label: string;
|
||||
tc_url: string;
|
||||
};
|
||||
currency?: {
|
||||
symbol: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue