Skip to content

Commit

Permalink
Add single product page route/component to the marketplace (#70)
Browse files Browse the repository at this point in the history
* Split marketplace route using `useMatch`

* Fix `MarketplaceSkeleton` component eslint

* Improve `ProductPage` component code

* Add "newfold-marketplace/v1/products/page" Api endpoint

* Refactor `MarketplaceSkeleton` component

- Improve styles to fit new ui
- use `classNames` package to handle custom styles.
-

* Product page loading state component

* Product page error state component

* Product page cypress test

* Require `uuid` package

* Dynamically render secondaryCTA

* Update marketplace tests to include product page secondary cta

* Only render valid url values for secondary cta in premium plugins tab

* Use `add/product-pages` Bluehost branch for tests

* Fix secondary cta url pattern

* remove failOnNonZeroExit flag

* Fix lint

* Fix another lint error

* Remove `plugin-branch` in cypress
  • Loading branch information
wpalani authored Jun 18, 2024
1 parent de2f082 commit 2d5064b
Show file tree
Hide file tree
Showing 18 changed files with 597 additions and 44 deletions.
21 changes: 21 additions & 0 deletions components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Marketplace from './marketplace';
import ProductPage from './productPage';

const NewfoldMarketplace = ( { methods, constants, ...props } ) => {
const match = methods.useMatch( 'marketplace/product/:id' );
if ( match ) {
return (
<ProductPage
productPageId={ match.params.id }
methods={ methods }
constants={ constants }
/>
);
}

return (
<Marketplace methods={ methods } constants={ constants } { ...props } />
);
};

export default NewfoldMarketplace;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import MarketplaceSkeleton from "../marketplaceSkeleton";
*/
const MarketplaceFilterBarSkeleton = ({ width, height }) => {
return (
<MarketplaceSkeleton width={ width || "500px" } height={ height || "45px" } customClass="filterbar-skeleton"/>
<MarketplaceSkeleton width={ width || "500px" } height={ height || "45px" } className="filterbar-skeleton"/>
);
}

Expand Down
12 changes: 6 additions & 6 deletions components/marketplaceIsLoading/MarketplaceItemSkeleton.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ const MarketplaceItemSkeleton = () => {
<div className="marketplace-item-skeleton">

<div className="skeleton-item-media">
<MarketplaceSkeleton width="100%" height="auto" customClass="marketplace-item-img-skeleton" />
<MarketplaceSkeleton width="100%" height="auto" className="marketplace-item-img-skeleton" />
</div>

<div className="skeleton-item-body">
<MarketplaceSkeleton width="170px" height="20px" customClass="marketplace-item-title-skeleton"/>
<MarketplaceSkeleton width="170px" height="20px" className="marketplace-item-title-skeleton"/>

<MarketplaceSkeleton width="100%" height="10px" customClass="marketplace-item-desc-skeleton"/>
<MarketplaceSkeleton width="100%" height="10px" customClass="marketplace-item-desc-skeleton"/>
<MarketplaceSkeleton width="70%" height="10px" customClass="marketplace-item-desc-skeleton"/>
<MarketplaceSkeleton width="100%" height="10px" className="marketplace-item-desc-skeleton"/>
<MarketplaceSkeleton width="100%" height="10px" className="marketplace-item-desc-skeleton"/>
<MarketplaceSkeleton width="70%" height="10px" className="marketplace-item-desc-skeleton"/>

<MarketplaceSkeleton width="80px" height="36px" customClass="marketplace-item-button-skeleton"/>
<MarketplaceSkeleton width="80px" height="36px" className="marketplace-item-button-skeleton"/>
</div>

</div>
Expand Down
64 changes: 50 additions & 14 deletions components/marketplaceItem/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { validate as isUuid } from 'uuid';
import { Button, Card, Link, Title } from '@newfold/ui-component-library';
import { ArrowRightIcon } from '@heroicons/react/24/outline';

Expand Down Expand Up @@ -150,6 +151,54 @@ const MarketplaceItem = ( { item, methods, constants } ) => {
return primaryCTA;
};

const renderSecondaryCTA = ( item ) => {
let secondaryCTA = '';
// If value is UUID, it is an internal link to a prodct page
const isInternal = isUuid( item.secondaryUrl );

const getLinkAttributes = () => {
const attributes = {
as: 'a',
className:
'nfd-inline-flex nfd-items-center nfd-gap-1.5 nfd-w-max nfd-no-underline',
href: generateSecondaryUrl(),
target: isInternal ? '_self' : '_blank',
onClick: handleNavigate,
};

return attributes;
};

const handleNavigate = ( e ) => {
const navigate = methods.useNavigate();
if ( isInternal ) {
e.preventDefault();
navigate( `/marketplace/product/${ item.secondaryUrl }` );
}
};

const generateSecondaryUrl = () => {
if ( isInternal ) {
return `${ window.NewfoldRuntime.admin_url }admin.php?page=bluehost#/marketplace/product/${ item.secondaryUrl }`;
}

return item.secondaryUrl;
};

if ( item.secondaryCallToAction && item.secondaryUrl ) {
secondaryCTA = (
<Link { ...getLinkAttributes() }>
<span className="nfd-text-primary">
{ item.secondaryCallToAction }
</span>
<ArrowRightIcon className="nfd-text-[#18181B] nfd-w-3" />
</Link>
);
}

return secondaryCTA;
};

const renderPrice = ( item ) => {
let pricewrap,
price,
Expand Down Expand Up @@ -198,20 +247,7 @@ const MarketplaceItem = ( { item, methods, constants } ) => {
{ item.name }
</Title>
<p>{ item.description }</p>

{ item.secondaryCallToAction && (
<Link
as="a"
href={ item.secondaryUrl }
target="_blank"
className="nfd-inline-flex nfd-items-center nfd-gap-1.5 nfd-w-max nfd-no-underline"
>
<span className="nfd-text-primary">
{ item.secondaryCallToAction }
</span>
<ArrowRightIcon className="nfd-text-[#18181B] nfd-w-3" />
</Link>
) }
{ renderSecondaryCTA( item ) }
</Card.Content>
<Card.Footer className="nfd-flex nfd-justify-between nfd-items-center nfd-flex-wrap nfd-gap-2 marketplace-item-footer">
{ renderPrice( item ) }
Expand Down
38 changes: 21 additions & 17 deletions components/marketplaceSkeleton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ import './stylesheet.scss';

/**
* MarketplaceSkeleton Component
* Use to generate content skeleton
*
* @param {*} props
* @returns
* Use to generate content loading skeleton
*
* @param {*} props
* @return {JSX.Element} MarketplaceSkeleton
*/
const MarketplaceSkeleton = ({ width, height, customClass }) => {
return (
<div
className={ "newfold-marketplace-skeleton " + ( customClass ) }
style={{
"width": width || "100%",
"height": height || "auto"
}}>
</div>
);
}

export default MarketplaceSkeleton;
const MarketplaceSkeleton = ( { width, height, className = '' } ) => {
return (
<div
// eslint-disable-next-line prettier/prettier
className={ classNames(
'newfold-marketplace-skeleton',
className
) }
style={ {
width: width || '100%',
height: height || 'auto',
} }
></div>
);
};

export default MarketplaceSkeleton;
2 changes: 1 addition & 1 deletion components/marketplaceSkeleton/stylesheet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
position: relative;
background-color: rgba(160, 170, 192, .35);
overflow: hidden;
border-radius: 2px;
border-radius: 4px;

&::after {
position: absolute;
Expand Down
32 changes: 32 additions & 0 deletions components/productPage/ProductPageError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Title } from '@newfold/ui-component-library';
import errorVector from '../../includes/assets/img/dog-walking.svg';

const ProductPageError = ( { text = {} } ) => {
return (
<div aria-live="assertive">
<div
role="alert"
className="nfd-flex nfd-flex-col nfd-items-center nfd-justify-center nfd-gap-6 nfd-p-8 nfd-min-h-[calc(100dvh-220px)]"
>
<img
src={ errorVector }
role="presentation"
alt="Dog walking with a leash"
width="300"
height="278.5"
/>
<div className="nfd-text-center">
<Title as="h2" size="1">
{ text?.title ?? 'Oops! Something Went Wrong' }
</Title>
<p className="nfd-mt-2 nfd-text-base">
{ text?.description ??
'An error occurred while loading the content. Please try again later.' }
</p>
</div>
</div>
</div>
);
};

export default ProductPageError;
129 changes: 129 additions & 0 deletions components/productPage/ProductPageLoading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import MarketplaceSkeleton from '../marketplaceSkeleton';

const ProductPageLoading = () => {
const Hero = () => (
<div className="nfd-min-h-[35vh] nfd-flex nfd-flex-col nfd-items-center nfd-justify-center nfd-bg-[#f0f3f9] nfd-m-3 nfd-rounded-lg">
<MarketplaceSkeleton
width="35%"
height="35px"
className="nfd-mb-4"
/>
<div className="nfd-flex nfd-flex-col nfd-items-center nfd-gap-2 nfd-w-full">
<MarketplaceSkeleton width="31%" height="15px" />
<MarketplaceSkeleton width="40%" height="15px" />
<MarketplaceSkeleton width="20%" height="15px" />
</div>
</div>
);

const Features = () => (
<div className="nfd-py-10 nfd-flex nfd-flex-col nfd-items-center">
<MarketplaceSkeleton
width="28%"
height="25px"
className="nfd-mb-3"
/>
<MarketplaceSkeleton
width="20%"
height="15px"
className="nfd-mb-1.5"
/>
<MarketplaceSkeleton width="12%" height="15px" />
<div className="nfd-mt-8 nfd-flex nfd-justify-between nfd-w-10/12">
<Feature />
<Feature />
<Feature />
</div>
</div>
);

const Feature = () => (
<div className="nfd-w-1/4 nfd-flex nfd-flex-col nfd-items-center">
<MarketplaceSkeleton
width="65px"
height="65px"
className="nfd-mb-4 nfd-rounded-full"
/>
<MarketplaceSkeleton
width="100%"
height="24px"
className="nfd-mb-2.5"
/>
<div className="nfd-flex nfd-flex-col nfd-items-center nfd-gap-1.5 nfd-w-full">
<MarketplaceSkeleton width="90%" height="15px" />
<MarketplaceSkeleton width="88%" height="15px" />
<MarketplaceSkeleton width="60%" height="15px" />
</div>
</div>
);

const Pricing = () => (
<>
<div className="nfd-w-full nfd-mt-3 nfd-flex nfd-flex-col nfd-items-center">
<div className="nfd-w-10/12 nfd-min-h-0.5 nfd-bg-[#f0f3f9]"></div>
</div>
<div className="nfd-py-10 nfd-flex nfd-flex-col nfd-items-center">
<MarketplaceSkeleton width="22%" height="27px" />
<div className="nfd-mt-10 nfd-flex nfd-justify-between nfd-w-10/12">
<PricingItem />
<PricingItem />
<PricingItem />
</div>
</div>
</>
);

const PricingItem = () => (
<div className="nfd-w-1/4 nfd-flex nfd-flex-col nfd-gap-7 nfd-bg-[#f0f3f9] nfd-rounded-lg nfd-p-6">
<MarketplaceSkeleton width="90%" height="28px" />
<div className="nfd-flex nfd-flex-col nfd-gap-4">
<MarketplaceSkeleton width="80%" height="14px" />
<MarketplaceSkeleton width="55%" height="14px" />
<MarketplaceSkeleton width="100%" height="14px" />
<MarketplaceSkeleton width="72%" height="14px" />
<MarketplaceSkeleton width="47%" height="14px" />
<MarketplaceSkeleton width="80%" height="14px" />
</div>
<MarketplaceSkeleton width="55%" height="34px" />
</div>
);

const FAQ = () => (
<div className="nfd-flex nfd-flex-col nfd-items-center nfd-gap-8 nfd-py-12 nfd-mx-auto nfd-w-7/12">
<FAQitem width="96%">
<div className="nfd-flex nfd-flex-col nfd-gap-2.5 nfd-mt-4">
<MarketplaceSkeleton width="100%" height="14px" />
<MarketplaceSkeleton width="91%" height="14px" />
<MarketplaceSkeleton width="95%" height="14px" />
<MarketplaceSkeleton width="54%" height="14px" />
</div>
</FAQitem>
<FAQitem />
<FAQitem />
<FAQitem />
<FAQitem />
</div>
);

const FAQitem = ( { width = '100%', children } ) => (
<div className="nfd-w-full nfd-bg-[#f0f3f9] nfd-rounded-lg nfd-p-4">
<MarketplaceSkeleton width={ width } height="26px" />
{ children }
</div>
);

return (
<div
aria-busy="true"
aria-live="polite"
aria-label="Fetching product details"
>
<Hero />
<Features />
<Pricing />
<FAQ />
</div>
);
};

export default ProductPageLoading;
Loading

0 comments on commit 2d5064b

Please sign in to comment.