diff --git a/admin/widgets.ts b/admin/widgets.ts index dda7b3c32..6ed9942ee 100644 --- a/admin/widgets.ts +++ b/admin/widgets.ts @@ -69,4 +69,3 @@ export type CSVWidget = string; * @accept application/pdf */ export type PDFWidget = string; - diff --git a/vnda/loaders/extensions/price/list.ts b/vnda/loaders/extensions/price/list.ts new file mode 100644 index 000000000..c8904f980 --- /dev/null +++ b/vnda/loaders/extensions/price/list.ts @@ -0,0 +1,21 @@ +import { Product } from "../../../../commerce/types.ts"; +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { fetchAndApplyPrices } from "../../../utils/transform.ts"; + +export interface Props { + priceCurrency: string; +} + +const loader = ( + { priceCurrency }: Props, + req: Request, + ctx: AppContext, +): ExtensionOf => +(products: Product[] | null) => { + if (!Array.isArray(products)) return products; + + return fetchAndApplyPrices(products, priceCurrency, req, ctx); +}; + +export default loader; diff --git a/vnda/loaders/extensions/price/listingPage.ts b/vnda/loaders/extensions/price/listingPage.ts new file mode 100644 index 000000000..6131c01b7 --- /dev/null +++ b/vnda/loaders/extensions/price/listingPage.ts @@ -0,0 +1,35 @@ +import { ProductListingPage } from "../../../../commerce/types.ts"; +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { fetchAndApplyPrices } from "../../../utils/transform.ts"; + +export interface Props { + priceCurrency: string; +} + +const loader = ( + { priceCurrency }: Props, + req: Request, + ctx: AppContext, +): ExtensionOf => +async (props: ProductListingPage | null) => { + if (!props) return props; + + const { products, ...page } = props; + + if (!Array.isArray(products)) return props; + + const extendedProducts = await fetchAndApplyPrices( + products, + priceCurrency, + req, + ctx, + ); + + return { + ...page, + products: extendedProducts, + }; +}; + +export default loader; diff --git a/vnda/manifest.gen.ts b/vnda/manifest.gen.ts index 1ed131159..fac2ad1fb 100644 --- a/vnda/manifest.gen.ts +++ b/vnda/manifest.gen.ts @@ -9,20 +9,24 @@ import * as $$$$$$$$$3 from "./actions/cart/updateItem.ts"; import * as $$$$$$$$$4 from "./actions/notifyme.ts"; import * as $$$$0 from "./handlers/sitemap.ts"; import * as $$$0 from "./loaders/cart.ts"; -import * as $$$1 from "./loaders/productDetailsPage.ts"; -import * as $$$2 from "./loaders/productDetailsPageVideo.ts"; -import * as $$$3 from "./loaders/productList.ts"; -import * as $$$4 from "./loaders/productListingPage.ts"; -import * as $$$5 from "./loaders/proxy.ts"; +import * as $$$1 from "./loaders/extensions/price/list.ts"; +import * as $$$2 from "./loaders/extensions/price/listingPage.ts"; +import * as $$$3 from "./loaders/productDetailsPage.ts"; +import * as $$$4 from "./loaders/productDetailsPageVideo.ts"; +import * as $$$5 from "./loaders/productList.ts"; +import * as $$$6 from "./loaders/productListingPage.ts"; +import * as $$$7 from "./loaders/proxy.ts"; const manifest = { "loaders": { "vnda/loaders/cart.ts": $$$0, - "vnda/loaders/productDetailsPage.ts": $$$1, - "vnda/loaders/productDetailsPageVideo.ts": $$$2, - "vnda/loaders/productList.ts": $$$3, - "vnda/loaders/productListingPage.ts": $$$4, - "vnda/loaders/proxy.ts": $$$5, + "vnda/loaders/extensions/price/list.ts": $$$1, + "vnda/loaders/extensions/price/listingPage.ts": $$$2, + "vnda/loaders/productDetailsPage.ts": $$$3, + "vnda/loaders/productDetailsPageVideo.ts": $$$4, + "vnda/loaders/productList.ts": $$$5, + "vnda/loaders/productListingPage.ts": $$$6, + "vnda/loaders/proxy.ts": $$$7, }, "handlers": { "vnda/handlers/sitemap.ts": $$$$0, diff --git a/vnda/utils/transform.ts b/vnda/utils/transform.ts index 3339f8205..84133429f 100644 --- a/vnda/utils/transform.ts +++ b/vnda/utils/transform.ts @@ -7,6 +7,8 @@ import { Seo, UnitPriceSpecification, } from "../../commerce/types.ts"; +import { STALE } from "../../utils/fetch.ts"; +import { AppContext } from "../mod.ts"; import { ProductGroup, ProductPrice, SEO } from "./client/types.ts"; import { OpenAPI, @@ -16,8 +18,9 @@ import { ProductVariant, VariantProductSearch, } from "./openapi/vnda.openapi.gen.ts"; +import { getSegmentFromCookie, parse } from "./segment.ts"; -type VNDAProductGroup = ProductSearch | OProduct; +export type VNDAProductGroup = ProductSearch | OProduct; type VNDAProduct = VariantProductSearch | ProductVariant; interface ProductOptions { @@ -80,7 +83,7 @@ export const parseSlug = (slug: string) => { }; }; -const pickVariant = ( +export const pickVariant = ( variants: VNDAProductGroup["variants"], variantId: string | null, normalize = true, @@ -129,7 +132,7 @@ const normalizeInstallments = ( const toURL = (src: string) => src.startsWith("//") ? `https:${src}` : src; -const toOffer = ({ +export const toOffer = ({ price, sale_price, intl_price, @@ -501,3 +504,54 @@ export const addVideoToProduct = ( ...(video ? toImageObjectVideo(video) : []), ], }); + +export const fetchAndApplyPrices = async ( + products: Product[], + priceCurrency: string, + req: Request, + ctx: AppContext, +): Promise => { + const segmentCookie = getSegmentFromCookie(req); + const segment = segmentCookie ? parse(segmentCookie) : null; + + const pricePromises = products.map((product) => + ctx.api["GET /api/v2/products/:productId/price"]({ + productId: product.sku, + coupon_codes: segment?.cc ? [segment.cc] : [], + }, STALE) + ); + + const priceResults = await Promise.all(pricePromises); + + return products.map((product) => { + const matchingPriceInfo = priceResults.find((priceResult) => + (priceResult as unknown as ProductPrice).variants.some((variant) => + variant.sku === product.sku + ) + ) as unknown as ProductPrice; + + const variantPrices = matchingPriceInfo?.variants + ? pickVariant( + matchingPriceInfo.variants as VNDAProductGroup["variants"], + product.sku, + false, + ) + : null; + + if (!variantPrices) return product; + + const offers = toOffer(variantPrices); + + return { + ...product, + offers: { + "@type": "AggregateOffer" as const, + priceCurrency: priceCurrency, + highPrice: variantPrices?.price ?? product.offers?.highPrice ?? 0, + lowPrice: variantPrices?.sale_price ?? product.offers?.lowPrice ?? 0, + offerCount: offers.length, + offers: offers, + }, + }; + }); +};