diff --git a/blocks/subscriptions-block/_index.scss b/blocks/subscriptions-block/_index.scss index 9110d00343..6c263861e8 100644 --- a/blocks/subscriptions-block/_index.scss +++ b/blocks/subscriptions-block/_index.scss @@ -136,24 +136,40 @@ @include scss.block-properties("checkout-payment"); } - &__test { - &-1 { - @include scss.block-components("checkout-payment-test-1"); - @include scss.block-properties("checkout-payment-test-1"); + &__orderCard { + &--productPrice { + @include scss.block-components("checkout-order-card-productPrice"); + @include scss.block-properties("checkout-order-card-productPrice"); } - &-2 { - @include scss.block-components("checkout-payment-test-2"); - @include scss.block-properties("checkout-payment-test-2"); + &__card--features { + &--feature-item { + @include scss.block-components("checkout-order-card--features--item"); + @include scss.block-properties("checkout-order-card--features--item"); + } + @include scss.block-components("checkout-order-card--features"); + @include scss.block-properties("checkout-order-card--features"); } - &-row { - @include scss.block-components("checkout-payment-test-row"); - @include scss.block-properties("checkout-payment-test-row"); + &__summary { + &--dueToday{ + @include scss.block-components("checkout-order-card--summary--dueToday"); + @include scss.block-properties("checkout-order-card--summary--dueToday"); + } + &--details{ + &--item{ + @include scss.block-components("checkout-order-card--summary--details--item"); + @include scss.block-properties("checkout-order-card--summary--details--item"); + } + @include scss.block-components("checkout-order-card--summary--details"); + @include scss.block-properties("checkout-order-card--summary--details"); + } + @include scss.block-components("checkout-order-card--summary"); + @include scss.block-properties("checkout-order-card--summary"); } - @include scss.block-components("checkout-payment-test"); - @include scss.block-properties("checkout-payment-test"); + @include scss.block-components("checkout-order-card"); + @include scss.block-properties("checkout-order-card"); } @include scss.block-components("checkout"); diff --git a/blocks/subscriptions-block/components/OfferCard/index.jsx b/blocks/subscriptions-block/components/OfferCard/index.jsx index 7cd4adb89d..f026511480 100644 --- a/blocks/subscriptions-block/components/OfferCard/index.jsx +++ b/blocks/subscriptions-block/components/OfferCard/index.jsx @@ -2,7 +2,25 @@ import React from "react"; import PropTypes from "@arc-fusion/prop-types"; import { Heading, Button, Stack, Paragraph, Icon } from "@wpmedia/arc-themes-components"; -// TO-DO: Change the Icon to Check +export const FeatureDetails = ({ features, className }) => { + if (features.length) { + return ( + + ); + } + return null; +}; + const OfferCard = ({ headline, subHeadline, @@ -21,20 +39,7 @@ const OfferCard = ({ ) : null} - - {features.length ? ( -
    - {features.map((feat) => ( -
  • - - -
  • - ))} -
- ) : null} + ); @@ -46,8 +51,8 @@ OfferCard.propTypes = { features: PropTypes.arrayOf( PropTypes.shape({ featureText: PropTypes.string, - }) + }), ), }; -export default OfferCard; \ No newline at end of file +export default OfferCard; diff --git a/blocks/subscriptions-block/components/OfferToProductList/index.jsx b/blocks/subscriptions-block/components/OfferToProductList/index.jsx index 2fc47c5a55..83b72bcc66 100644 --- a/blocks/subscriptions-block/components/OfferToProductList/index.jsx +++ b/blocks/subscriptions-block/components/OfferToProductList/index.jsx @@ -4,6 +4,9 @@ import useSales from "../useSales"; import { Grid } from "@wpmedia/arc-themes-components"; import OfferCard from "../OfferCard"; +export const ARCXP_CART = 'ArcXP_cart'; +export const ARCXP_CAMPAIGN = 'ArcXP_campaignName'; + const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, className }) => { const { Sales } = useSales(); @@ -21,7 +24,7 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam products[productIdx].attributes.length !== 0 ? products[productIdx].attributes.map((feature) => ({ featureText: feature.value, - })) + })) : []; const { sku } = products[productIdx]; const { priceCode } = strategies[strategiesIdx]; @@ -32,21 +35,25 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam actionText: strategies[strategiesIdx].summary, actionEvent: () => { Sales.clearCart() - .then(() => + .then(() => { Sales.addItemToCart([ { sku, priceCode, quantity: 1, }, - ]) - ) + ]); + const maxEndDate = Math.max(...offer?.campaigns?.map(c => c.validUntil)); + const liveCampaing = offer?.campaigns?.find(c => c.validUntil === null || c.validUntil === maxEndDate); + localStorage.setItem(ARCXP_CAMPAIGN, liveCampaing?.name); + }) .then(() => { if (isLoggedIn) { window.location.href = checkoutURL; return; } - window.location.href = `${loginURL}?redirect=${checkoutURL}`; + localStorage.setItem(ARCXP_CART, JSON.stringify({sku, priceCode})); + window.location.href = `${loginURL}&redirect=${checkoutURL}`; }); }, features, @@ -75,7 +82,7 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam className={className} /> ))} - + ); }; diff --git a/blocks/subscriptions-block/components/OrderInformation/index.jsx b/blocks/subscriptions-block/components/OrderInformation/index.jsx new file mode 100644 index 0000000000..0b29fa355e --- /dev/null +++ b/blocks/subscriptions-block/components/OrderInformation/index.jsx @@ -0,0 +1,88 @@ +import React from "react"; + +import { usePhrases, Link, Heading, Paragraph, Stack } from "@wpmedia/arc-themes-components"; +import { FeatureDetails } from "../OfferCard"; +import currency from "../../utils/currency"; + +const OrderSummary = ({ orderDetails, className }) => { + const phrases = usePhrases(); + + return ( + + {phrases.t("checkout-block.order-summary")} + + +

{phrases.t("checkout-block.subtotal")}

+

{`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`}

+
+ +

{phrases.t("checkout-block.salesTax")}

+

+ {orderDetails?.tax > 0 + ? `${currency(orderDetails?.currency)}${orderDetails?.tax}` + : "--"} +

+
+
+
+

{phrases.t("checkout-block.due-today")}

+

{`${currency(orderDetails?.currency)}${orderDetails?.total}`}

+
+
+ ); +}; + +const OrderInformation = ({ + offerURL, + showOfferURL, + showPriceDescription, + showProductFeatures, + orderDetails, + className, +}) => { + const phrases = usePhrases(); + + const ProductPriceDetails = ({ + details = [], + showPriceDescription, + showProductFeatures, + className, + }) => { + if (details?.items?.length) { + return details?.items?.map((item) => { + return ( + + {item.priceName} + {showPriceDescription && ( + + )} + {showProductFeatures && ( + + )} + + ); + }); + } + return null; + }; + + return ( +
+ + {showOfferURL && ( + {phrases.t("checkout-block.view-subscription-offers")} + )} + +
+ ); +}; + +export default OrderInformation; diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.js b/blocks/subscriptions-block/components/OrderInformation/index.test.js new file mode 100644 index 0000000000..74712e1e4f --- /dev/null +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.js @@ -0,0 +1,104 @@ +import React from "react"; +import { screen, render, within } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import OrderInformation from "./index"; +import currency from "../../utils/currency"; + + +jest.mock("@arc-publishing/sdk-identity", () => ({ + __esModule: true, + default: { + apiOrigin: "", + options: jest.fn(), + }, +})); + +jest.mock("fusion:properties", () => + jest.fn(() => ({ + api: { + identity: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + }, + retail: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + endpoint: "/retail/public/v1/offer/live/", + }, + }, + })), +); + +jest.mock("fusion:context", () => ({ + __esModule: true, + useFusionContext: () => ({ + arcSite: "Test Site", + }), +})); + +const orderDetails = { + currency: "COP", + shipping: 0, + subtotal: 20000, + tax: 100, + taxSupported: true, + total: 20100, + items: [ + { + name: "COP Currency", + price: 20000, + priceCode: "Q6R7UO", + priceDescription: "

with tax description price

", + priceName: "All access Annual", + priceSummary: "

with tax summary price

", + productDescription: "

COP Currency description

", + quantity: 1, + shortDescription: "

COP Currency description

", + sku: "0987", + subtotal: 20100, + tax: 0, + taxInclusive: undefined, + total: 20000, + productAttributes: [ + { + featureText: "

Unlimited access to The Daily Intelligencer

", + }, + { + featureText: "

Save $40

", + }, + { + featureText: "

A bonus subscription to share

", + }, + ], + }, + ], +}; + +describe("OfferCard", () => { + it("renders order info", ()=>{ + render(); + expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); + expect(screen.getByText("checkout-block.order-summary")).toBeVisible(); + expect(screen.getByText("checkout-block.subtotal")).toBeVisible(); + expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`)).toBeVisible(); + expect(screen.getByText("checkout-block.salesTax")).toBeVisible(); + expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.total}`)).toBeVisible(); + expect(screen.getByText("checkout-block.due-today")).toBeVisible(); + }); + + it("renders price description", ()=>{ + render(); + expect(screen.getByText("with tax description price")).toBeVisible(); + }); + + it("renders features", () => { + render(); + const list = screen.getByRole("list") + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(3); + }); + + it("renders link to offer", ()=>{ + render(); + expect(screen.getByRole('link', { name: 'checkout-block.view-subscription-offers' })).toHaveAttribute('href', '/offer/'); + }); +}); diff --git a/blocks/subscriptions-block/components/useOrder.jsx b/blocks/subscriptions-block/components/useOrder.jsx new file mode 100644 index 0000000000..3e0fc1fc26 --- /dev/null +++ b/blocks/subscriptions-block/components/useOrder.jsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from "react"; + +import { useSales } from "@wpmedia/arc-themes-components"; +import { ARCXP_CAMPAIGN } from "./OfferToProductList"; +import useOffer from "./useOffer"; + +const useOrder = (orderNumber) => { + const { Sales } = useSales(); + + const [order, setOrder] = useState(); + const [orderDetails, setOrderDetails] = useState(); + const [cart, setCart] = useState(); + const [cartDetails, setCartDetails] = useState(); + const [error, setError] = useState(); + + const [campaignName, setCampaignName] = useState(); + + const { offer } = useOffer({ + campaignCode: campaignName, + }); + + useEffect(() => { + const getCart = async () => { + const cart = await Sales.getCart(); + setCart(cart); + }; + getCart(); + + const campaignName = localStorage.getItem(ARCXP_CAMPAIGN); + setCampaignName(campaignName); + }, []); + + useEffect(() => { + const getOrder = async () => { + try{ + const order = await Sales.getOrderDetails(orderNumber); + setOrder(order); + }catch(e){ + setError(e); + } + }; + if (orderNumber) { + getOrder(); + } + }, [orderNumber]); + + useEffect(() => { + const getProductPriceDetailsFromOffer = (sku, priceCode) => { + let productAttributes; + let productDescription; + let priceName; + let priceDescription; + let priceSummary; + let rates; + + const productBySku = offer?.products?.find((item) => item?.sku === sku); + + if (productBySku) { + productAttributes = + typeof productBySku?.attributes !== "undefined" && productBySku?.attributes?.length !== 0 + ? productBySku?.attributes.map((feature) => ({ + featureText: feature.value, + })) + : []; + productDescription = productBySku?.description; + const pricingStrategy = productBySku?.pricingStrategies?.find((price)=> price?.priceCode === priceCode); + + priceName = pricingStrategy?.name; + priceDescription = pricingStrategy?.description; + priceSummary = pricingStrategy?.summary; + rates = pricingStrategy?.rates; + } + + return { + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + }; + }; + + if (offer) { + if (order) { + const itemsDetail = order?.items?.map((item) => { + const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates } = getProductPriceDetailsFromOffer( + item?.sku, + item?.priceCode, + ); + return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates}; + }); + setOrderDetails({...order, items: itemsDetail}) + } else if (cart) { + const itemsDetail = cart?.items?.map((item) => { + const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive } = getProductPriceDetailsFromOffer( + item?.sku, + item?.priceCode, + ); + return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive}; + }); + setCartDetails({...cart, items: itemsDetail}) + } + } + }, [order, cart, offer]); + + return { + cartDetails, + orderDetails, + error + }; +}; + +export default useOrder; diff --git a/blocks/subscriptions-block/components/usePaymentRedirect.jsx b/blocks/subscriptions-block/components/usePaymentRedirect.jsx index d1806039b3..570d6a4aa4 100644 --- a/blocks/subscriptions-block/components/usePaymentRedirect.jsx +++ b/blocks/subscriptions-block/components/usePaymentRedirect.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import useSales from "./useSales"; +import { useSales } from "@wpmedia/arc-themes-components"; import { LABEL_ORDER_NUMBER_PAYPAL } from "../features/checkout/default"; export const usePaymentRedirect = ( diff --git a/blocks/subscriptions-block/intl.json b/blocks/subscriptions-block/intl.json index 07358dc45b..9f22f06403 100644 --- a/blocks/subscriptions-block/intl.json +++ b/blocks/subscriptions-block/intl.json @@ -6848,5 +6848,14 @@ "vi": "Cập nhật phương thức thanh toán", "zh-CN": "更新付款方式", "zh-TW": "更新付款方式" + }, + "checkout-block.view-subscription-offers": { + "en": "View subscription offers" + }, + "checkout-block.subtotal": { + "en": "Subtotal" + }, + "checkout-block.salesTax": { + "en": "Sales tax" } } \ No newline at end of file diff --git a/blocks/subscriptions-block/themes/news.json b/blocks/subscriptions-block/themes/news.json index a1223d662f..5c8940e0c7 100644 --- a/blocks/subscriptions-block/themes/news.json +++ b/blocks/subscriptions-block/themes/news.json @@ -401,6 +401,126 @@ } } }, + "checkout-order-card": { + "styles": { + "default": { + "display": "flex", + "flex-direction": "column", + "padding-block-start": "var(--global-spacing-4)", + "padding-inline-end": "var(--global-spacing-4)", + "padding-block-end": "var(--global-spacing-4)", + "padding-inline-start": "var(--global-spacing-4)", + "gap": "var(--global-spacing-5)", + "column-gap": "var(--global-spacing-5)", + "components": { + "link": { + "color": "var(--text-color)", + "text-decoration": "underline", + "font-size": "var(--global-font-size-4)", + "font-weight": "var(--global-font-weight-4)" + } + } + } + } + }, + "checkout-order-card-productPrice": { + "styles": { + "default": { + "display": "flex", + "flex-direction": "column", + "gap": "var(--global-spacing-5)", + "column-gap": "var(--global-spacing-5)", + "components": { + "heading": { + "font-size": "var(--global-font-size-9)", + "font-weight": "var(--global-font-weight-7)", + "line-height": "var(--global-line-height-6)" + } + } + } + } + }, + "checkout-order-card--features": { + "styles": { + "default": { + "align-self": "baseline" + } + } + }, + "checkout-order-card--features--item": { + "styles": { + "default": { + "align-items": "center", + "display": "flex", + "margin-block-end": "var(--global-spacing-2)", + "components": { + "icon": { + "fill": "var(--status-color-success)", + "display": "flex", + "inline-size": "var(--global-spacing-4)", + "block-size": "var(--global-spacing-4)", + "margin-block-start": "0", + "margin-inline-end": "var(--global-spacing-2)", + "margin-block-end": "0", + "margin-inline-start": "0" + } + } + } + } + }, + "checkout-order-card--summary": { + "styles": { + "default": { + "gap": "var(--global-spacing-2)", + "components": { + "heading": { + "font-family": "var(--font-family-secondary)", + "font-weight": "var(--global-font-weight-7)", + "font-size": "var(--global-font-size-4)", + "padding-block-end": "var(--global-spacing-2)", + "border-block-end-width": "1px", + "border-block-end-style": "solid", + "border-block-end-color": "var(--border-color)" + } + } + } + } + }, + "checkout-order-card--summary--dueToday": { + "styles": { + "default": { + "display": "flex", + "justify-content": "space-between", + "padding-block-start": "var(--global-spacing-2)", + "border-block-start-width": "1px", + "border-block-start-style": "solid", + "border-block-start-color": "var(--border-color)", + "font-family": "var(--font-family-secondary)", + "font-weight": "var(--global-font-weight-7)", + "font-size": "var(--global-font-size-7)", + "inline-size": "100%", + "align-items": "flex-start" + } + } + }, + "checkout-order-card--summary--details":{ + "styles": { + "default": { + "gap": "var(--global-spacing-2)" + } + } + }, + "checkout-order-card--summary--details--item": { + "styles": { + "default": { + "flex-direction": "row", + "justify-content": "space-between", + "font-family": "var(--font-family-primary)", + "font-weight": "var(--global-font-weight-4)", + "font-size": "var(--global-font-size-3)" + } + } + }, "offer": { "styles": { "default": { diff --git a/locale/en.json b/locale/en.json index cb45782a46..77ed7228a4 100644 --- a/locale/en.json +++ b/locale/en.json @@ -455,6 +455,7 @@ "checkout-block.Zambia": "Zambia", "checkout-block.Zimbabwe": "Zimbabwe", "checkout-block.back-to-offer-page": "Back to subscription selection", + "checkout-block.view-subscription-offers": "View subscription offers", "checkout-block.cardNumber": "Card number", "checkout-block.cardholderName": "Name on card", "checkout-block.cardholderName-requirements": "Please enter name on card", @@ -463,6 +464,8 @@ "checkout-block.country": "Country or region", "checkout-block.country-requirements": "Please select country or region", "checkout-block.due-today": "Due today", + "checkout-block.subtotal": "Subtotal", + "checkout-block.salesTax": "Sales tax", "checkout-block.email": "Email address", "checkout-block.email-requirements": "Please enter a valid email address", "checkout-block.empty-cart-message": "Select from one of our offers",