Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 127 additions & 24 deletions app/components/product/badges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@ import type {
} from "storefront-api.generated";
import { cn } from "~/utils/cn";

export interface BadgeStyleSettings {
colorText: string;
colorTextInverse: string;
badgeBorderRadius: number;
badgeTextTransform: string;
}

function Badge({
text,
backgroundColor,
badgeStyle,
className,
}: {
text: string;
backgroundColor: string;
badgeStyle: BadgeStyleSettings;
className?: string;
}) {
const { colorText, colorTextInverse, badgeBorderRadius, badgeTextTransform } =
useThemeSettings();
let { colorText, colorTextInverse, badgeBorderRadius, badgeTextTransform } =
badgeStyle;
return (
<span
style={{
Expand All @@ -37,52 +46,90 @@ function Badge({

export function NewBadge({
publishedAt,
badgeStyle,
newBadgeText,
newBadgeColor,
newBadgeDaysOld,
className,
}: {
publishedAt: string;
badgeStyle: BadgeStyleSettings;
newBadgeText: string;
newBadgeColor: string;
newBadgeDaysOld: number;
className?: string;
}) {
const { newBadgeText, newBadgeColor, newBadgeDaysOld } = useThemeSettings();
if (isNewArrival(publishedAt, newBadgeDaysOld)) {
return (
<Badge
text={newBadgeText}
backgroundColor={newBadgeColor}
badgeStyle={badgeStyle}
className={clsx("new-badge", className)}
/>
);
}
return null;
}

export function BestSellerBadge({ className }: { className?: string }) {
const { bestSellerBadgeText, bestSellerBadgeColor } = useThemeSettings();
export function BestSellerBadge({
badgeStyle,
bestSellerBadgeText,
bestSellerBadgeColor,
className,
}: {
badgeStyle: BadgeStyleSettings;
bestSellerBadgeText: string;
bestSellerBadgeColor: string;
className?: string;
}) {
return (
<Badge
text={bestSellerBadgeText}
backgroundColor={bestSellerBadgeColor}
badgeStyle={badgeStyle}
className={clsx("best-seller-badge", className)}
/>
);
}

export function SoldOutBadge({ className }: { className?: string }) {
const { soldOutBadgeText, soldOutBadgeColor } = useThemeSettings();
export function SoldOutBadge({
badgeStyle,
soldOutBadgeText,
soldOutBadgeColor,
className,
}: {
badgeStyle: BadgeStyleSettings;
soldOutBadgeText: string;
soldOutBadgeColor: string;
className?: string;
}) {
return (
<Badge
text={soldOutBadgeText}
backgroundColor={soldOutBadgeColor}
badgeStyle={badgeStyle}
className={clsx("sold-out-badge", className)}
/>
);
}

export function BundleBadge({ className }: { className?: string }) {
const { bundleBadgeText, bundleBadgeColor } = useThemeSettings();
export function BundleBadge({
badgeStyle,
bundleBadgeText,
bundleBadgeColor,
className,
}: {
badgeStyle: BadgeStyleSettings;
bundleBadgeText: string;
bundleBadgeColor: string;
className?: string;
}) {
return (
<Badge
text={bundleBadgeText}
backgroundColor={bundleBadgeColor}
badgeStyle={badgeStyle}
className={clsx("bundle-badge", className)}
/>
);
Expand All @@ -91,16 +138,21 @@ export function BundleBadge({ className }: { className?: string }) {
export function SaleBadge({
price,
compareAtPrice,
badgeStyle,
saleBadgeText = "Sale",
saleBadgeColor,
className,
}: {
price: MoneyV2;
compareAtPrice: MoneyV2;
badgeStyle: BadgeStyleSettings;
saleBadgeText?: string;
saleBadgeColor: string;
className?: string;
}) {
const { saleBadgeText = "Sale", saleBadgeColor } = useThemeSettings();
const { amount, percentage } = calculateDiscount(price, compareAtPrice);
const discountAmount = useMoney({ amount, currencyCode: price.currencyCode });
const text = saleBadgeText
let { amount, percentage } = calculateDiscount(price, compareAtPrice);
let discountAmount = useMoney({ amount, currencyCode: price.currencyCode });
let text = saleBadgeText
.replace("[amount]", discountAmount.withoutTrailingZeros)
.replace("[percentage]", percentage);

Expand All @@ -109,6 +161,7 @@ export function SaleBadge({
<Badge
text={text}
backgroundColor={saleBadgeColor}
badgeStyle={badgeStyle}
className={clsx("sale-badge", className)}
/>
);
Expand All @@ -118,8 +171,8 @@ export function SaleBadge({

function calculateDiscount(price: MoneyV2, compareAtPrice: MoneyV2) {
if (price?.amount && compareAtPrice?.amount) {
const priceNumber = Number(price.amount);
const compareAtPriceNumber = Number(compareAtPrice.amount);
let priceNumber = Number(price.amount);
let compareAtPriceNumber = Number(compareAtPrice.amount);
if (compareAtPriceNumber > priceNumber) {
return {
amount: String(compareAtPriceNumber - priceNumber),
Expand Down Expand Up @@ -150,18 +203,43 @@ export function ProductBadges({
className?: string;
as?: React.ElementType;
}) {
let {
colorText,
colorTextInverse,
badgeBorderRadius,
badgeTextTransform,
newBadgeText,
newBadgeColor,
newBadgeDaysOld,
bestSellerBadgeText,
bestSellerBadgeColor,
soldOutBadgeText,
soldOutBadgeColor,
bundleBadgeText,
bundleBadgeColor,
saleBadgeText,
saleBadgeColor,
} = useThemeSettings();

let badgeStyle: BadgeStyleSettings = {
colorText,
colorTextInverse,
badgeBorderRadius,
badgeTextTransform,
};

if (!(product && selectedVariant)) {
return null;
}

const isBundle = Boolean(product?.isBundle?.requiresComponents);
const { publishedAt, badges } = product;
const isBestSellerProduct = badges
let isBundle = Boolean(product?.isBundle?.requiresComponents);
let { publishedAt, badges } = product;
let isBestSellerProduct = badges
.filter(Boolean)
.some(({ key, value }) => key === "best_seller" && value === "true");

const isFragment = Component.toString() === "Symbol(react.fragment)";
const componentProps = isFragment
let isFragment = Component.toString() === "Symbol(react.fragment)";
let componentProps = isFragment
? {}
: {
className: cn(
Expand All @@ -174,16 +252,41 @@ export function ProductBadges({
<Component {...componentProps}>
{selectedVariant.availableForSale ? (
<>
{isBundle && <BundleBadge />}
{isBundle && (
<BundleBadge
badgeStyle={badgeStyle}
bundleBadgeText={bundleBadgeText}
bundleBadgeColor={bundleBadgeColor}
/>
)}
<SaleBadge
price={selectedVariant.price as MoneyV2}
compareAtPrice={selectedVariant.compareAtPrice as MoneyV2}
badgeStyle={badgeStyle}
saleBadgeText={saleBadgeText}
saleBadgeColor={saleBadgeColor}
/>
<NewBadge
publishedAt={publishedAt}
badgeStyle={badgeStyle}
newBadgeText={newBadgeText}
newBadgeColor={newBadgeColor}
newBadgeDaysOld={newBadgeDaysOld}
/>
<NewBadge publishedAt={publishedAt} />
{isBestSellerProduct && <BestSellerBadge />}
{isBestSellerProduct && (
<BestSellerBadge
badgeStyle={badgeStyle}
bestSellerBadgeText={bestSellerBadgeText}
bestSellerBadgeColor={bestSellerBadgeColor}
/>
)}
</>
) : (
<SoldOutBadge />
<SoldOutBadge
badgeStyle={badgeStyle}
soldOutBadgeText={soldOutBadgeText}
soldOutBadgeColor={soldOutBadgeColor}
/>
)}
</Component>
);
Expand Down
60 changes: 55 additions & 5 deletions app/components/product/product-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import JudgemeStarsRating from "~/sections/main-product/judgeme-stars-rating";
import { isCombinedListing } from "~/utils/combined-listings";
import { calculateAspectRatio } from "~/utils/image";
import {
type BadgeStyleSettings,
BestSellerBadge,
BundleBadge,
NewBadge,
Expand All @@ -34,7 +35,7 @@ export function ProductCard({
product: ProductCardFragment;
className?: string;
}) {
const {
let {
pcardBorderRadius,
pcardBackgroundColor,
pcardShowImageOnHover,
Expand All @@ -55,8 +56,30 @@ export function ProductCard({
pcardShowBestSellerBadge,
pcardShowNewBadge,
pcardShowOutOfStockBadge,
colorText,
colorTextInverse,
badgeBorderRadius,
badgeTextTransform,
newBadgeText,
newBadgeColor,
newBadgeDaysOld,
bestSellerBadgeText,
bestSellerBadgeColor,
soldOutBadgeText,
soldOutBadgeColor,
bundleBadgeText,
bundleBadgeColor,
saleBadgeText,
saleBadgeColor,
} = useThemeSettings();

let badgeStyle: BadgeStyleSettings = {
colorText,
colorTextInverse,
badgeBorderRadius,
badgeTextTransform,
};

const [selectedVariant, setSelectedVariant] =
useState<ProductVariantFragment | null>(null);
const [isImageLoading, setIsImageLoading] = useState(false);
Expand Down Expand Up @@ -145,18 +168,45 @@ export function ProductCard({
</Link>
)}
<div className="absolute top-2.5 right-2.5 flex gap-1">
{isBundle && pcardShowBundleBadge && <BundleBadge />}
{isBundle && pcardShowBundleBadge && (
<BundleBadge
badgeStyle={badgeStyle}
bundleBadgeText={bundleBadgeText}
bundleBadgeColor={bundleBadgeColor}
/>
)}
{pcardShowSaleBadge && (
<SaleBadge
price={minVariantPrice as MoneyV2}
compareAtPrice={maxVariantPrice as MoneyV2}
badgeStyle={badgeStyle}
saleBadgeText={saleBadgeText}
saleBadgeColor={saleBadgeColor}
/>
)}
{pcardShowBestSellerBadge && isBestSellerProduct && (
<BestSellerBadge />
<BestSellerBadge
badgeStyle={badgeStyle}
bestSellerBadgeText={bestSellerBadgeText}
bestSellerBadgeColor={bestSellerBadgeColor}
/>
)}
{pcardShowNewBadge && (
<NewBadge
publishedAt={product.publishedAt}
badgeStyle={badgeStyle}
newBadgeText={newBadgeText}
newBadgeColor={newBadgeColor}
newBadgeDaysOld={newBadgeDaysOld}
/>
)}
{pcardShowOutOfStockBadge && (
<SoldOutBadge
badgeStyle={badgeStyle}
soldOutBadgeText={soldOutBadgeText}
soldOutBadgeColor={soldOutBadgeColor}
/>
)}
{pcardShowNewBadge && <NewBadge publishedAt={product.publishedAt} />}
{pcardShowOutOfStockBadge && <SoldOutBadge />}
</div>
{pcardEnableQuickShop && (
<QuickShopTrigger
Expand Down
6 changes: 4 additions & 2 deletions app/sections/single-product/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Button } from "~/components/button";
import { Image } from "~/components/image";
import Link from "~/components/link";
import { AddToCartButton } from "~/components/product/add-to-cart-button";
import { ProductBadges, SoldOutBadge } from "~/components/product/badges";
import { ProductBadges } from "~/components/product/badges";
import { BundledVariants } from "~/components/product/bundled-variants";
import { ProductMedia } from "~/components/product/product-media";
import { Quantity } from "~/components/product/quantity";
Expand Down Expand Up @@ -67,7 +67,9 @@ export default function SingleProduct(props: SingleProductProps) {
sizes="auto"
/>
<div className="flex flex-col items-start justify-start gap-4">
<SoldOutBadge />
<span className="sold-out-badge px-1.5 py-1 text-sm uppercase">
Sold out
</span>
<h3 data-motion="fade-up" className="tracking-tight">
EXAMPLE PRODUCT TITLE
</h3>
Expand Down
Loading