Skip to content

Sale price display updates #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 21, 2025
26 changes: 16 additions & 10 deletions examples/commerce-essentials/src/app/(store)/cart/CartItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Link from "next/link";
import { CartItem as CartItemType } from "@elasticpath/js-sdk";
import { LoadingDots } from "../../../components/LoadingDots";
import { getProductURLSegment } from "../../../lib/product-helper";
import PriceDisplay, { SalePriceDisplayStyle } from "../../../components/product/PriceDisplay";

export type CartItemProps = {
item: CartItemType;
Expand All @@ -15,6 +16,10 @@ export type CartItemProps = {
export function CartItem({ item, productSlug }: CartItemProps) {
const { mutate, isPending } = useCartRemoveItem();
const canonicalURL = getProductURLSegment({ id: item.product_id, attributes: { slug: productSlug } });
const display_original_price =
item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount;
return (
<div className="flex gap-5">
<div className="flex w-16 sm:w-24 h-20 sm:h-[7.5rem] justify-center shrink-0 items-start">
Expand All @@ -31,16 +36,17 @@ export function CartItem({ item, productSlug }: CartItemProps) {
</span>
</div>
<div className="flex h-7 gap-2 flex-col">
<span className="font-medium">
{item.meta.display_price.with_tax.value.formatted}
</span>
{item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount && (
<span className="text-black/60 text-sm line-through">
{item.meta.display_price.without_discount?.value.formatted}
</span>
)}
<PriceDisplay
display_price={item.meta.display_price.with_tax.value}
original_display_price={
display_original_price &&
item.meta.display_price.without_discount?.value
}
salePriceDisplay={SalePriceDisplayStyle.strikePriceWithCalcValue}
showCurrency={false}
priceDisplayStyleOverride="text-xl text-gray-900"
saleCalcDisplayStyleOverride="pr-4 text-sm font-light text-red-500 content-center"
/>
</div>
</div>
<div className="flex w-[15rem] gap-5 items-center">
Expand Down
23 changes: 13 additions & 10 deletions examples/commerce-essentials/src/app/(store)/cart/CartItemWide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import { NumberInput } from "../../../components/number-input/NumberInput";
import { CartItemProps } from "./CartItem";
import { LoadingDots } from "../../../components/LoadingDots";
import { getProductURLSegment } from "../../../lib/product-helper";
import PriceDisplay, { SalePriceDisplayStyle } from "../../../components/product/PriceDisplay";

export function CartItemWide({ item, productSlug }: CartItemProps) {
const { mutate, isPending } = useCartRemoveItem();
const canonicalURL = getProductURLSegment({ id: item.product_id, attributes: { slug: productSlug } });
const display_original_price = item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount;
return (
<div className="flex gap-5 self-stretch">
{/* Thumbnail */}
Expand Down Expand Up @@ -46,16 +50,15 @@ export function CartItemWide({ item, productSlug }: CartItemProps) {
</div>
</div>
<div className="flex lg:pl-14 flex-col h-7 items-end">
<span className="font-medium">
{item.meta.display_price.with_tax.value.formatted}
</span>
{item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount && (
<span className="text-black/60 text-sm line-through">
{item.meta.display_price.without_discount?.value.formatted}
</span>
)}
<PriceDisplay
display_price={item.meta.display_price.with_tax.value}
original_display_price={
display_original_price &&
item.meta.display_price.without_discount?.value
}
showCurrency={false}
salePriceDisplay={SalePriceDisplayStyle.strikePriceWithCalcValue}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"use client";
import { ProductThumbnail } from "../../app/(store)/account/orders/[orderId]/ProductThumbnail";
import Link from "next/link";
import { CartItem } from "@elasticpath/js-sdk";
import Link from "next/link";
import { ProductThumbnail } from "../../app/(store)/account/orders/[orderId]/ProductThumbnail";
import { getProductURLSegment } from "../../lib/product-helper";
import PriceDisplay, { SalePriceDisplayStyle } from "../product/PriceDisplay";

export function CheckoutItem({ item, productSlug }: { item: CartItem, productSlug?: string }) {
const canonicalURL = getProductURLSegment({ id: item.product_id, attributes: { slug: productSlug } });
const display_original_price =
item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount;
return (
<div className="flex w-full lg:w-[24.375rem] gap-5 items-start">
<div className="flex flex-col w-[4.5rem] h-[5.626rem] justify-start shrink-0 items-center">
Expand All @@ -18,16 +23,15 @@ export function CheckoutItem({ item, productSlug }: { item: CartItem, productSlu
<span className="text-sm text-black/60">Quantity: {item.quantity}</span>
</div>
<div className="flex flex-col items-center gap-2">
<span className="font-medium">
{item.meta.display_price.with_tax.value.formatted}
</span>
{item.meta.display_price.without_discount?.value.amount &&
item.meta.display_price.without_discount?.value.amount !==
item.meta.display_price.with_tax.value.amount && (
<span className="text-black/60 text-sm line-through">
{item.meta.display_price.without_discount?.value.formatted}
</span>
)}
<PriceDisplay
display_price={item.meta.display_price.with_tax.value}
original_display_price={
display_original_price &&
item.meta.display_price.without_discount?.value
}
showCurrency={false}
salePriceDisplay={SalePriceDisplayStyle.strikePriceWithCalcValue}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use server";
import clsx from "clsx";
import Link from "next/link";
import { ArrowRightIcon, EyeSlashIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Image from "next/image";
import Link from "next/link";
import { getServerSideImplicitClient } from "../../lib/epcc-server-side-implicit-client";
import { getProductDisplayPrices, getProductURLSegment } from "../../lib/product-helper";
import PriceDisplay, { SalePriceDisplayStyle } from "../product/PriceDisplay";
import { fetchFeaturedProducts } from "./fetchFeaturedProducts";
import { getProductURLSegment } from "../../lib/product-helper";
import { FormattedPrice } from "@elasticpath/js-sdk";

interface IFeaturedProductsProps {
title: string;
Expand All @@ -21,6 +23,16 @@ export default async function FeaturedProducts({
}: IFeaturedProductsProps) {
const client = getServerSideImplicitClient();
const products = await fetchFeaturedProducts(client);
const productPriceMap = new Map<
string,
{
displayPrice: FormattedPrice | unknown;
originalPrice: FormattedPrice | unknown;
}
>();
for (const product of products) {
productPriceMap.set(product.id, getProductDisplayPrices(product));
};

return (
<div
Expand Down Expand Up @@ -75,9 +87,18 @@ export default async function FeaturedProducts({
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900">
{product.attributes.name}
</p>
<p className="pointer-events-none block text-sm font-medium text-gray-500">
{product.meta.display_price?.without_tax?.formatted}
</p>
<PriceDisplay
display_price={productPriceMap.get(product.id)?.displayPrice}
original_display_price={
productPriceMap.get(product.id)?.originalPrice
}
showCurrency={false}
salePriceDisplay={
SalePriceDisplayStyle.strikePriceWithCalcValue
}
priceDisplayStyleOverride="text-base text-gray-500"
saleCalcDisplayStyleOverride="pr-4 text-sm font-light text-red-500 content-center"
/>
</li>
</Link>
))}
Expand Down
14 changes: 11 additions & 3 deletions examples/commerce-essentials/src/components/product/Price.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import clsx from "clsx";

interface IPriceProps {
price: string;
currency: string;
size?: string;
styleOverride?: string;
activeSalePrice?: boolean;
}

const Price = ({ price, currency, size }: IPriceProps): JSX.Element => {
const Price = ({
price,
currency,
styleOverride,
activeSalePrice
}: IPriceProps): JSX.Element => {
return (
<span
className={`mt-4 font-light text-gray-900 ${size ? size : "text-2xl"}`}
className={clsx(activeSalePrice && "font-bold", styleOverride ? styleOverride : "text-2xl text-gray-900")}
>
{price} {currency}
</span>
Expand Down
108 changes: 108 additions & 0 deletions examples/commerce-essentials/src/components/product/PriceDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Price from "./Price";
import SaleDisplay from "./SaleDisplay";
import StrikePrice from "./StrikePrice";

interface IPriceDisplayProps {
display_price: any;
salePriceDisplay: SalePriceDisplayStyle;
original_display_price: any;
showCurrency?: boolean;
priceDisplayStyleOverride?: string;
saleCalcDisplayStyleOverride?: string;
}

export enum SalePriceDisplayStyle {
none = "none",
strikePrice = "strike-price",
strikePriceWithCalcValue = "strike-price-with-calc-value",
strikePriceWithCalcPercent = "strike-price-with-calc-percent"
}

const PriceDisplay = ({
display_price,
original_display_price,
salePriceDisplay,
showCurrency,
priceDisplayStyleOverride,
saleCalcDisplayStyleOverride,
}: IPriceDisplayProps): JSX.Element => {
const currentPrice = (
<Price
price={display_price?.formatted}
currency={showCurrency ? display_price?.currency : ""}
styleOverride={priceDisplayStyleOverride}
activeSalePrice={original_display_price && salePriceDisplay !== SalePriceDisplayStyle.none}
/>
);

const currentStrikePrice = original_display_price && (
<StrikePrice
price={original_display_price.formatted}
currency={showCurrency ? original_display_price.currency : ""}
/>
);

let displayValue;
if (
original_display_price &&
salePriceDisplay === SalePriceDisplayStyle.strikePriceWithCalcValue
) {
const amountOff =
(original_display_price.amount - display_price.amount) / 100;
const amountOffDisplay =
"-" +
new Intl.NumberFormat("en", {
style: "currency",
currency: display_price.currency,
trailingZeroDisplay: "stripIfInteger",
}).format(amountOff);
displayValue = (
<SaleDisplay
value={amountOffDisplay}
styleOverride={saleCalcDisplayStyleOverride}
/>
);
} else if (
original_display_price &&
salePriceDisplay === SalePriceDisplayStyle.strikePriceWithCalcPercent
) {
const amountOff = original_display_price.amount - display_price.amount;
const percentOff = amountOff / original_display_price.amount;
const percentOffDisplay =
"-" +
new Intl.NumberFormat("en", {
style: "percent",
roundingMode: "trunc",
}).format(percentOff);
displayValue = (
<SaleDisplay
value={percentOffDisplay}
styleOverride={saleCalcDisplayStyleOverride}
/>
);
}
return (
<div className="flex flex-col mt-4 items-end">
{salePriceDisplay === SalePriceDisplayStyle.none && currentPrice}
{salePriceDisplay === SalePriceDisplayStyle.strikePrice && (
<>
{currentPrice}
{currentStrikePrice}
</>
)}
{(salePriceDisplay === SalePriceDisplayStyle.strikePriceWithCalcValue ||
salePriceDisplay ===
SalePriceDisplayStyle.strikePriceWithCalcPercent) && (
<>
<div className="flex flex-row">
{displayValue}
{currentPrice}
</div>
{currentStrikePrice}
</>
)}
</div>
);
};

export default PriceDisplay;
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useContext } from "react";
import Price from "./Price";
import StrikePrice from "./StrikePrice";
import clsx from "clsx";
import type { ShopperProduct } from "@elasticpath/react-shopper-hooks";
import clsx from "clsx";
import { useContext } from "react";
import { ProductContext } from "../../lib/product-context";
import PriceDisplay, { SalePriceDisplayStyle } from "./PriceDisplay";
import { getProductDisplayPrices } from "../../lib/product-helper";
import { ProductResponse } from "@elasticpath/js-sdk";

interface IProductSummary {
product: ShopperProduct["response"];
Expand All @@ -12,32 +13,26 @@ interface IProductSummary {
const ProductSummary = ({ product }: IProductSummary): JSX.Element => {
const {
attributes,
meta: { display_price, original_display_price },
} = product;
const context = useContext(ProductContext);

const { displayPrice, originalPrice } = getProductDisplayPrices(
product as ProductResponse,
);
return (
<div
className={clsx(context?.isChangingSku && "opacity-20 cursor-default")}
>
<span className="text-xl font-semibold leading-[1.1] sm:text-3xl lg:text-4xl">
{attributes.name}
</span>
{display_price && (
<div className="flex items-center">
<Price
price={display_price.without_tax.formatted}
currency={display_price.without_tax.currency}
size="text-2xl"
/>
{original_display_price && (
<StrikePrice
price={original_display_price.without_tax.formatted}
currency={original_display_price.without_tax.currency}
/>
)}
</div>
)}
<div className="flex items-center">
<PriceDisplay
display_price={displayPrice}
original_display_price={originalPrice}
showCurrency={false}
salePriceDisplay={SalePriceDisplayStyle.strikePriceWithCalcValue}
/>
</div>
</div>
);
};
Expand Down
Loading
Loading