diff --git a/js/src/data/actions.js b/js/src/data/actions.js index 50ec2dacc1..b26a2e0e6b 100644 --- a/js/src/data/actions.js +++ b/js/src/data/actions.js @@ -95,6 +95,24 @@ export function handleFetchError( error, message ) { * @property {boolean} [contact_info_visible] Whether the phone number, email, and/or address are visible on the website. */ +/** + * @typedef {Object} ProductStatisticsDetails + * @property {number} active Number of active products. + * @property {number} expiring Number of expiring products. + * @property {number} pending Number of pending products. + * @property {number} disapproved Number of disapproved products. + * @property {number} not_synced Number of not synced products. + */ + +/** + * Product status statistics on Google Merchant Center + * + * @typedef {Object} ProductStatistics + * @property {number} scheduled_sync Number of scheduled jobs which will sync products to Google. + * @property {number} timestamp Timestamp reflecting when the product status statistics were last generated. + * @property {ProductStatisticsDetails} statistics Statistics information of product status on Google Merchant Center. + */ + /** * * @return {Array} Array of individual shipping rates. @@ -871,6 +889,13 @@ export function* receiveMCSetup( mcSetup ) { }; } +/** + * Creates a wp-data action with data payload to be dispatched the received + * MC product statistics to wp-data store. + * + * @param {ProductStatistics} mcProductStatistics The received MC product statistics data. + * @yield {Object} The wp-data action with data payload. + */ export function* receiveMCProductStatistics( mcProductStatistics ) { return { type: TYPES.RECEIVE_MC_PRODUCT_STATISTICS, diff --git a/js/src/data/selectors.js b/js/src/data/selectors.js index 234d4a5613..c75f4c3523 100644 --- a/js/src/data/selectors.js +++ b/js/src/data/selectors.js @@ -129,6 +129,17 @@ export const getMCSetup = ( state ) => { return state.mc_setup; }; +/** + * @typedef {import('.~/data/actions').ProductStatistics } ProductStatistics + */ + +/** + * Get the MC product statistics data. + * + * @param {Object} state The current store state will be injected by `wp.data`. + * + * @return {ProductStatistics|null} The MC product statistics data. Returns `null` if data have not yet loaded. + */ export const getMCProductStatistics = ( state ) => { return state.mc_product_statistics; }; diff --git a/js/src/product-feed/product-statistics/status-box/sync-status.js b/js/src/product-feed/product-statistics/status-box/sync-status.js index cd5444d610..43d012425a 100644 --- a/js/src/product-feed/product-statistics/status-box/sync-status.js +++ b/js/src/product-feed/product-statistics/status-box/sync-status.js @@ -12,15 +12,17 @@ import Status from '.~/product-feed/product-statistics/status-box/status'; import { glaData } from '.~/constants'; import SyncIcon from '.~/components/sync-icon'; import SuccessIcon from '.~/components/success-icon'; +import getNumberOfSyncProducts from '.~/utils/getNumberOfSyncProducts'; + +/** + * @typedef {import('.~/data/actions').ProductStatistics } ProductStatistics + */ /** * Returns the text as well as the icon an description for the Sync Status * based on the `scheduled_sync` value as the synced number of products. * - * @param {Object} data Data with the sync information - * @param {number} data.scheduled_sync Amount of scheduled jobs which will sync products to Google. - * @param {Object} data.statistics Merchant Center product status statistics information. - * @param {number} data.timestamp Timestamp reflecting when the product status statistics were last generated. + * @param {ProductStatistics} data Product status statistics on Google Merchant Center * @return {Object} The icon, status and description of the sync process. */ function getSyncResult( { @@ -36,15 +38,7 @@ function getSyncResult( { }; } - const totalSynced = Object.entries( statistics ).reduce( - ( sum, [ key, num ] ) => { - if ( key === 'not_synced' ) { - return sum; - } - return sum + num; - }, - 0 - ); + const totalSynced = getNumberOfSyncProducts( statistics ); return { Icon: SuccessIcon, diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.js new file mode 100644 index 0000000000..4fa748a599 --- /dev/null +++ b/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.js @@ -0,0 +1,113 @@ +/** + * External dependencies + */ +import { sprintf, __, _n } from '@wordpress/i18n'; +import { Flex, FlexItem, FlexBlock } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import Section from '.~/wcdl/section'; +import AppDocumentationLink from '.~/components/app-documentation-link'; +import SyncIcon from '.~/components/sync-icon'; +import AppTooltip from '.~/components/app-tooltip'; +import getNumberOfSyncProducts from '.~/utils/getNumberOfSyncProducts'; +import './product-feed-status-section.scss'; + +function ProductQuantity( { quantity } ) { + if ( ! Number.isInteger( quantity ) ) { + return null; + } + + const text = sprintf( + // translators: %d: number of products will be synced to Google Merchant Center. + _n( '%d product', '%d products', quantity, 'google-listings-and-ads' ), + quantity + ); + + return ( + <> + + + { text } + + + ); +} + +// TODO: `href`` is not yet ready. Will be added later. +/** + * @fires gla_documentation_link_click with `{ context: 'setup-paid-ads', link_id: 'product-feed-status-learn-more', href: 'https://example.com' }` + */ + +/** + * Renders a section layout to elaborate on how the product listings will be processed + * and show the number of products will be synced to Google Merchant Center. + */ +export default function ProductFeedStatusSection() { + /* + const { data, hasFinishedResolution } = useAppSelectDispatch( + 'getMCProductStatistics' + ); + */ + // TODO: Replace the dummy data with the above code later to use the adjusted API. + const data = { + statistics: { + active: 1, + expiring: 2, + pending: 3, + disapproved: 4, + not_synced: 5, + }, + }; + const hasFinishedResolution = true; + const productQuantity = hasFinishedResolution + ? getNumberOfSyncProducts( data.statistics ) + : null; + + return ( +
+ { __( 'Learn more', 'google-listings-and-ads' ) } + + } + > + + + + + + + + + { __( + 'Your product listings are being uploaded', + 'google-listings-and-ads' + ) } + + + { __( + 'Google will review your product listings within 3-5 days. Once approved, your products will automatically be live and searchable on Google. You’ll be notified if there are any product feed issues.', + 'google-listings-and-ads' + ) } + + + + +
+ ); +} diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.scss b/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.scss new file mode 100644 index 0000000000..a8f901388f --- /dev/null +++ b/js/src/setup-mc/setup-stepper/setup-paid-ads/product-feed-status-section.scss @@ -0,0 +1,36 @@ +.gla-product-feed-status-section { + .gla-sync-icon { + fill: $gla-color-green; + transform: rotateZ(90deg); + } + + .wcdl-subsection-title { + display: flex; + align-items: center; + } + + &__product-quantity-separator, + .app-tooltip__children-container { + font-size: $gla-font-smallest; + font-weight: normal; + color: $gray-700; + } + + &__product-quantity-separator::before { + content: "•"; + display: inline-block; + margin: 0 $grid-unit-10; + } + + .app-tooltip__children-container { + padding-bottom: calc($grid-unit-05 / 2); + line-height: 1; + border-bottom: $border-width dashed $gray-600; + } + + .components-popover__content { + width: 200px; + white-space: normal; + font-weight: normal; + } +} diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js index 847f1ea29e..86025ee2a4 100644 --- a/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js +++ b/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js @@ -16,6 +16,7 @@ import StepContentHeader from '.~/components/stepper/step-content-header'; import StepContentFooter from '.~/components/stepper/step-content-footer'; import FaqsSection from '.~/components/paid-ads/faqs-section'; import AppButton from '.~/components/app-button'; +import ProductFeedStatusSection from './product-feed-status-section'; import { getProductFeedUrl } from '.~/utils/urls'; import { GUIDE_NAMES } from '.~/constants'; import { API_NAMESPACE } from '.~/data/constants'; @@ -67,6 +68,7 @@ export default function SetupPaidAds() { 'google-listings-and-ads' ) } /> + diff --git a/js/src/utils/getNumberOfSyncProducts.js b/js/src/utils/getNumberOfSyncProducts.js new file mode 100644 index 0000000000..a5813e25f0 --- /dev/null +++ b/js/src/utils/getNumberOfSyncProducts.js @@ -0,0 +1,18 @@ +/** + * @typedef {import('.~/data/actions').ProductStatisticsDetails } ProductStatisticsDetails + */ + +/** + * Return the total number of syncing/synced products except for the `not_synced` category. + * + * @param {ProductStatisticsDetails} statistics The statistics data of scheduled synchronized products. + * @return {number} Number of syncing/synced products. + */ +export default function getNumberOfSyncProducts( statistics ) { + return Object.entries( statistics ).reduce( ( sum, [ key, num ] ) => { + if ( key === 'not_synced' ) { + return sum; + } + return sum + num; + }, 0 ); +}