Changes from `add/marketplace-product-card-component` after rebasing on feature branch.

This commit is contained in:
And Finally 2023-08-08 15:29:08 +01:00
parent 85b4011c13
commit b56654867a
15 changed files with 382 additions and 34 deletions

View File

@ -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 );

View File

@ -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;
}
}
}

View File

@ -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>
);
}

View File

@ -1 +1,6 @@
// To keep StyleLint happy. Remove when file contains actual code.
.woocommerce-marketplace {
&__extensions {
display: flex;
flex-direction: column;
}
}

View File

@ -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>
);
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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>
);
}

View File

@ -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;
}
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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 };

View File

@ -33,6 +33,9 @@ declare global {
cta_label: string;
tc_url: string;
};
currency?: {
symbol: string;
};
};
};
}