Skip to content
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

legacy offer #2597

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,10 @@ export const getSchema = async (options: Options) => {

export * from './platforms/vtex/resolvers/root'
export type { Resolver } from './platforms/vtex'

export type {
CommertialOffer,
Item,
ProductSearchResult,
Seller,
} from './platforms/vtex/clients/search/types/ProductSearchResult'
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (process.env.NODE_ENV === 'development' && !includeGTM) {

function ThirdPartyScripts() {
const forwards = []
if (includeVTEX) forwards.push('sendrc', 'vtexaf')
if (includeVTEX) forwards.push('sendrc')

return (
<>
Expand Down
29 changes: 21 additions & 8 deletions packages/core/src/components/ThirdPartyScripts/vtex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,31 @@ function VTEX() {
src="https://io.vtex.com.br/rc/rc.js"
/>
<script
key="vtex-af.js-init"
type="text/partytown"
type="text/javascript"
key="vtexaf-init"
dangerouslySetInnerHTML={{
__html: `f=window.vtexaf=window.vtexaf||function(){(f.q=f.q||[]).push(arguments)};f.l=+new Date`,
__html: `
(function(v,t,e,x,a,f,s){
f=v.vtexaf=v.vtexaf||function(){(f.q=f.q||[]).push(arguments)}
;f.l=+new Date;s=t.createElement(e);s.async=!0;
s.src=x;a=t.getElementsByTagName(e)[0];
a.parentNode.insertBefore(s,a)
})(window,document,'script','https://activity-flow.vtex.com/af/af.js');
`,
}}
/>
<script
key="vtex-af.js-script"
type="text/partytown"
data-an={storeConfig.api.storeId}
async
src="https://activity-flow.vtex.com/af/af.js"
type="text/javascript"
key="vtexaf-config"
dangerouslySetInnerHTML={{
__html: `
window.vtexaf('init', {
account: '${storeConfig.api.storeId}',
env: '${storeConfig.api.environment}',
workspace: '${storeConfig.api.workspace}'
});
`,
}}
/>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,61 +191,76 @@ function ProductDetails({
{...ImageGallery.props}
images={productImages}
/>
<section data-fs-product-details-info>
<section
data-fs-product-details-settings
data-fs-product-details-section
>
<ProductDetailsSettings.Component
buyButtonTitle={buyButtonTitle}
buyButtonIcon={buyButtonIcon}
notAvailableButtonTitle={
notAvailableButtonTitle ?? NotAvailableButton.props.title
}
useUnitMultiplier={quantitySelector?.useUnitMultiplier ?? false}
{...ProductDetailsSettings.props}
// Dynamic props shouldn't be overridable
// This decision can be reviewed later if needed
quantity={quantity}
setQuantity={setQuantity}
product={product}
isValidating={isValidating}
taxesConfiguration={taxesConfiguration}
/>
{isValidating ? (
<section data-fs-product-details-info>
<section
data-fs-product-details-settings
data-fs-product-details-section
>
<p>Loading...</p>
</section>
</section>

{!outOfStock && (
<ShippingSimulation.Component
) : (
<section data-fs-product-details-info>
<section
data-fs-product-details-settings
data-fs-product-details-section
data-fs-product-details-shipping
formatter={useFormattedPrice}
{...ShippingSimulation.props}
idkPostalCodeLinkProps={{
...ShippingSimulation.props.idkPostalCodeLinkProps,
href:
shippingSimulatorLinkUrl ??
ShippingSimulation.props.idkPostalCodeLinkProps?.href,
children:
shippingSimulatorLinkText ??
ShippingSimulation.props.idkPostalCodeLinkProps?.children,
}}
productShippingInfo={{
id,
quantity,
seller: seller.identifier,
}}
title={shippingSimulatorTitle ?? ShippingSimulation.props.title}
inputLabel={
shippingSimulatorInputLabel ??
ShippingSimulation.props.inputLabel
}
optionsLabel={
shippingSimulatorOptionsTableTitle ??
ShippingSimulation.props.optionsLabel
}
/>
)}
</section>
>
<ProductDetailsSettings.Component
buyButtonTitle={buyButtonTitle}
buyButtonIcon={buyButtonIcon}
notAvailableButtonTitle={
notAvailableButtonTitle ?? NotAvailableButton.props.title
}
useUnitMultiplier={
quantitySelector?.useUnitMultiplier ?? false
}
{...ProductDetailsSettings.props}
// Dynamic props shouldn't be overridable
// This decision can be reviewed later if needed
quantity={quantity}
setQuantity={setQuantity}
product={product}
isValidating={isValidating}
taxesConfiguration={taxesConfiguration}
/>
</section>

{!outOfStock && (
<ShippingSimulation.Component
data-fs-product-details-section
data-fs-product-details-shipping
formatter={useFormattedPrice}
{...ShippingSimulation.props}
idkPostalCodeLinkProps={{
...ShippingSimulation.props.idkPostalCodeLinkProps,
href:
shippingSimulatorLinkUrl ??
ShippingSimulation.props.idkPostalCodeLinkProps?.href,
children:
shippingSimulatorLinkText ??
ShippingSimulation.props.idkPostalCodeLinkProps?.children,
}}
productShippingInfo={{
id,
quantity,
seller: seller.identifier,
}}
title={
shippingSimulatorTitle ?? ShippingSimulation.props.title
}
inputLabel={
shippingSimulatorInputLabel ??
ShippingSimulation.props.inputLabel
}
optionsLabel={
shippingSimulatorOptionsTableTitle ??
ShippingSimulation.props.optionsLabel
}
/>
)}
</section>
)}

{shouldDisplayProductDescription && (
<ProductDescription.Component
Expand Down
21 changes: 15 additions & 6 deletions packages/core/src/pages/[slug]/p.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import GlobalSections, {
import PageProvider, { PDPContext } from 'src/sdk/overrides/PageProvider'
import { useProductQuery } from 'src/sdk/product/useProductQuery'
import { PDPContentType, getPDP } from 'src/server/cms/pdp'

import { getOfferUrl, useOffer } from 'src/sdk/offer'
import Head from 'next/head'
/**
* Sections: Components imported from each store's custom components and '../components/sections' only.
* Do not import or render components from any other folder in here.
Expand Down Expand Up @@ -70,20 +71,27 @@ function Page({ data: server, sections, globalSections, offers, meta }: Props) {
const { currency } = useSession()
const titleTemplate = storeConfig?.seo?.titleTemplate ?? ''

// Stale while revalidate the product for fetching the new price etc
const { data: client, isValidating } = useProductQuery(product.id, {
product: product,
})
const offer = useOffer({ skuId: product.sku })
const client = { product: { offers: offer.offers } }

const context = {
data: {
...deepmerge(server, client, { arrayMerge: overwriteMerge }),
isValidating,
isValidating: offer.isValidating,
},
} as PDPContext

return (
<GlobalSections {...globalSections}>
<Head>
<link
rel="preload"
href={getOfferUrl(product.sku)}
as="fetch"
crossOrigin="anonymous"
fetchPriority="high"
/>
</Head>
{/* SEO */}
<NextSeo
title={meta.title}
Expand Down Expand Up @@ -264,6 +272,7 @@ export const getStaticProps: GetStaticProps<
globalSections,
key: seo.canonical,
},
revalidate: 300,
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function Document() {
return (
<Html>
<Head>
<meta name="storefront" content="fast_store" />
{!process.env.DISABLE_3P_SCRIPTS && <ThirdPartyScripts />}
<WebFonts />
</Head>
Expand Down
52 changes: 52 additions & 0 deletions packages/core/src/sdk/offer/aggregate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Item, Seller } from '@faststore/api'
import { EnhancedCommercialOffer } from './enhance'
import { inStock, price } from './sort'

type Root = EnhancedCommercialOffer<Seller, Item>

const withTax = (
price: number,
tax: number = 0,
unitMultiplier: number = 1
) => {
const unitTax = tax / unitMultiplier
return Math.round((price + unitTax) * 100) / 100
}

const getHighPrice = (
offers: Root[],
options: { includeTaxes: boolean } = { includeTaxes: false }
) => {
const availableOffers = offers.filter(inStock)
const highOffer = availableOffers[availableOffers.length - 1]
const highPrice = highOffer ? price(highOffer) : 0
if (!options.includeTaxes) {
return highPrice
}

return withTax(highPrice, highOffer?.Tax, highOffer?.product?.unitMultiplier)
}

const getLowPrice = (
offers: Root[],
options: { includeTaxes: boolean } = { includeTaxes: false }
) => {
const [lowOffer] = offers.filter(inStock)

const lowPrice = lowOffer ? price(lowOffer) : 0

if (!options.includeTaxes) {
return lowPrice
}

return withTax(lowPrice, lowOffer?.Tax, lowOffer?.product?.unitMultiplier)
}

export function aggregateOffer(offers: Root[]) {
return {
highPrice: getHighPrice(offers),
lowPrice: getLowPrice(offers),
lowPriceWithTaxes: getLowPrice(offers, { includeTaxes: true }),
offerCount: offers.length,
}
}
20 changes: 20 additions & 0 deletions packages/core/src/sdk/offer/enhance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CommertialOffer } from '@faststore/api'

export type EnhancedCommercialOffer<S, P> = CommertialOffer & {
seller: S
product: P
}

export const enhanceCommercialOffer = <S, P>({
offer,
seller,
product,
}: {
offer: CommertialOffer
seller: S
product: P
}): EnhancedCommercialOffer<S, P> => ({
...offer,
product,
seller,
})
19 changes: 19 additions & 0 deletions packages/core/src/sdk/offer/fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ProductSearchResult } from '@faststore/api'
import { api, storeUrl } from '../../../faststore.config'

const IS_PROD = process.env.NODE_ENV === 'production'

export function getUrl(skuId: string) {
const base = IS_PROD
? storeUrl
: `https://${api.storeId}.${api.environment}.com.br`
const url = new URL(`${base}/api/intelligent-search/product_search`)
url.searchParams.append('query', `sku.id:${skuId}`)
return url.toString()
}

export async function fetcher(skuId: string) {
return fetch(getUrl(skuId)).then((res) =>
res.json()
) as Promise<ProductSearchResult>
}
45 changes: 45 additions & 0 deletions packages/core/src/sdk/offer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import useSWR from 'swr'
import { aggregateOffer } from './aggregate'
import { enhanceCommercialOffer } from './enhance'
import { fetcher } from './fetcher'
import { bestOfferFirst } from './sort'
export { getUrl as getOfferUrl } from './fetcher'

const ERROR_DATA = { offers: {}, isValidating: false }

export function useOffer(args: { skuId: string }) {
const { data, error, isValidating } = useSWR(args.skuId, fetcher)

if (error || !data || data.products.length === 0) {
console.warn('Error or no data fetching offer to SKU', args.skuId, error)
return ERROR_DATA
}

const product = data.products[0]

if (!product || product.items.length === 0) {
console.warn('Product not found or has no items for SKU', args.skuId)
return ERROR_DATA
}

const item = product.items.find((item) => item.itemId === args.skuId)

if (!item) {
console.warn('Item not found for SKU', args.skuId)
return ERROR_DATA
}

const sellers = item.sellers
.map((seller) =>
enhanceCommercialOffer({
offer: seller.commertialOffer,
seller,
product: item,
})
)
.sort(bestOfferFirst)

const offers = aggregateOffer(sellers)

return { offers, isValidating }
}
Loading
Loading