Skip to content

Commit

Permalink
refact(new-price-calculator): promote purchase summary to a separate …
Browse files Browse the repository at this point in the history
…component instead of a part of the purchase form
  • Loading branch information
guilhermespopolin committed Oct 21, 2024
1 parent 0ab7805 commit 3ea0a2e
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { ComparisonTableModal } from './ComparisonTableModal'
import { DeductibleSelector } from './DeductibleSelector'
import { DiscountTooltip } from './DiscountTooltip/DiscountTooltip'
import { useDiscountTooltipProps } from './DiscountTooltip/useDiscountTooltipProps'
import { usePriceIntent, useResetPriceIntent } from './priceIntentAtoms'
import { usePriceIntent } from './priceIntentAtoms'
import { ProductTierSelector } from './ProductTierSelector'
import { useSelectedOffer } from './useSelectedOffer'
import { useTiersAndDeductibles } from './useTiersAndDeductibles'
Expand Down Expand Up @@ -149,14 +149,12 @@ export const OfferPresenter = memo((props: Props) => {
[priceIntent.offers],
)

const resetPriceIntent = useResetPriceIntent()
const handleAddToCart: MouseEventHandler = (event) => {
event.preventDefault()
datadogRum.addAction(`PriceCalculator AddToCart Cart`)
setAddToCartRedirect(AddToCartRedirect.Cart)
onSuccessRef.current = (addedProductOffer: ProductOfferFragment) => {
props.notifyProductAdded(addedProductOffer)
resetPriceIntent()
}
addToCart(selectedOffer.id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { atom, useAtom, useAtomValue, useSetAtom, useStore } from 'jotai'
import { atomFamily } from 'jotai/utils'
import { useCallback, useEffect } from 'react'
import { useProductData } from '@/components/ProductData/ProductDataProvider'
import {
priceTemplateAtom,
usePriceTemplate,
} from '@/components/ProductPage/PurchaseForm/priceTemplateAtom'
import { priceTemplateAtom } from '@/components/ProductPage/PurchaseForm/priceTemplateAtom'
import { useSelectedOffer } from '@/components/ProductPage/PurchaseForm/useSelectedOffer'
import { useCartEntryToReplace } from '@/components/ProductPage/useCartEntryToReplace'
import {
Expand All @@ -19,7 +16,7 @@ import {
import { setupForm } from '@/services/PriceCalculator/PriceCalculator.helpers'
import type { Form } from '@/services/PriceCalculator/PriceCalculator.types'
import { priceIntentServiceInitClientSide } from '@/services/priceIntent/PriceIntentService'
import { useShopSession, useShopSessionId } from '@/services/shopSession/ShopSessionContext'
import { useShopSession } from '@/services/shopSession/ShopSessionContext'
import { getOffersByPrice } from '@/utils/getOffersByPrice'
import { getAtomValueOrThrow } from '@/utils/jotaiUtils'

Expand Down Expand Up @@ -182,8 +179,7 @@ export const useSyncPriceIntentState = ({
} else {
// This is a workaround as 'priceIntentAtoms' is used for new and old price calculator.
// For the old price calculator, we need to created a new price intent if there's a cart entry
// for that price intent. When we remove support for the old price calculator, we'll be able to
// remove of the config paramenters that can be provided to 'useSyncPriceIntentState'
// for that price intent.
if (replacePriceIntentWhenCurrentIsAddedToCart) {
const cartPriceIntentIds = new Set(cart?.entries.map((item) => item.priceIntentId))
if (cartPriceIntentIds.has(savedId)) {
Expand Down Expand Up @@ -291,22 +287,3 @@ export const usePriceIntentId = (): string => {
}
return priceIntentId
}

export const useResetPriceIntent = () => {
const shopSessionId = useShopSessionId()
// When we start using TemplateV2 exclusively, we can remove this
// and retrieve the name from the template directly
const productName = useProductData().name
const apolloClient = useApolloClient()
const priceTemplate = usePriceTemplate()
const setCurrentPriceIntentId = useSetAtom(currentPriceIntentIdAtom)
const priceIntentService = priceIntentServiceInitClientSide(apolloClient)

return useCallback(() => {
if (shopSessionId == null) return
setCurrentPriceIntentId(null)
priceIntentService.create({ productName, priceTemplate, shopSessionId }).then((priceIntent) => {
setCurrentPriceIntentId(priceIntent.id)
})
}, [priceIntentService, setCurrentPriceIntentId, shopSessionId, priceTemplate, productName])
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { style } from '@vanilla-extract/css'
import { responsiveStyles, tokens, yStack } from 'ui'
import { HEADER_HEIGHT_DESKTOP } from '@/components/Header/Header.css'
import { HEADER_HEIGHT_MOBILE } from '@/components/Header/HeaderMenuMobile/HeaderMenuMobile.css'
import { CONTENT_MAX_WIDTH } from '@/features/priceCalculator/PurchaseFormV2/PurchaseFormV2.css'

// Offset for fixed formFooter
export const FORM_FOOTER = '7rem'
Expand Down Expand Up @@ -65,3 +66,18 @@ export const productHero = style({
},
}),
})

export const purchaseSummaryWrapper = style([
yStack({ alignItems: 'center', justifyContent: 'center' }),
{ height: '100%' },
])

export const purchaseSummary = style({
width: `min(100%, ${CONTENT_MAX_WIDTH})`,
marginInline: 'auto',
...responsiveStyles({
lg: {
width: `max(100%, ${CONTENT_MAX_WIDTH})`,
},
}),
})
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use client'

import { useAtomValue } from 'jotai'
import { priceCalculatorShowPurchaseSummaryAtom } from '@/features/priceCalculator/priceCalculatorAtoms'
import { PurchaseSummary } from '@/features/priceCalculator/PurchaseFormV2/PurchaseSummary/PurchaseSummary'
import { useResponsiveVariant } from '@/utils/useResponsiveVariant'
import { priceCalculatorStepAtom } from '../../priceCalculatorAtoms'
import { ProductHeroV2 } from '../../ProductHeroV2/ProductHeroV2'
import { PurchaseFormV2 } from '../../PurchaseFormV2/PurchaseFormV2'
import { CartToast } from './CartToast/CartToast'
Expand All @@ -11,13 +12,15 @@ import {
priceCalculatorSection,
productHero,
productHeroSection,
purchaseSummaryWrapper,
purchaseSummary,
} from './PriceCalculatorCmsPageContent.css'

export function PriceCalculatorCmsPageContent() {
const variant = useResponsiveVariant('lg')
const step = useAtomValue(priceCalculatorStepAtom)
const showPurchaseSummary = useAtomValue(priceCalculatorShowPurchaseSummaryAtom)

const showProductHero = variant === 'desktop' || step !== 'purchaseSummary'
const showProductHero = variant === 'desktop' || !showPurchaseSummary

return (
<div className={pageGrid}>
Expand All @@ -27,7 +30,13 @@ export function PriceCalculatorCmsPageContent() {
</section>
)}
<section className={priceCalculatorSection}>
<PurchaseFormV2 />
{showPurchaseSummary ? (
<div className={purchaseSummaryWrapper}>
<PurchaseSummary className={purchaseSummary} />
</div>
) : (
<PurchaseFormV2 />
)}
</section>
{/* We don't mount CartToast on mobile as we hide ShoppingCartMenuItem at that level */}
{variant === 'desktop' && <CartToast />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useApolloClient } from '@apollo/client'
import { datadogLogs } from '@datadog/browser-logs'
import { datadogRum } from '@datadog/browser-rum'
import { useInView } from 'framer-motion'
Expand All @@ -19,21 +18,22 @@ import {
priceCalculatorFormAtom,
usePriceIntent,
} from '@/components/ProductPage/PurchaseForm/priceIntentAtoms'
import { usePriceTemplate } from '@/components/ProductPage/PurchaseForm/priceTemplateAtom'
import {
useSelectedOffer,
useSelectedOfferValueOrThrow,
} from '@/components/ProductPage/PurchaseForm/useSelectedOffer'
import { useTiersAndDeductibles } from '@/components/ProductPage/PurchaseForm/useTiersAndDeductibles'
import { useCartEntryToReplace } from '@/components/ProductPage/useCartEntryToReplace'
import { DiscountFieldContainer } from '@/components/ShopBreakdown/DiscountFieldContainer'
import { priceCalculatorStepAtom } from '@/features/priceCalculator/priceCalculatorAtoms'
import {
priceCalculatorStepAtom,
priceCalculatorShowPurchaseSummaryAtom,
} from '@/features/priceCalculator/priceCalculatorAtoms'
import { DeductibleSelectorV2 } from '@/features/priceCalculator/PurchaseFormV2/OfferPresenterV2/DeductibleSelectorV2/DeductibleSelectorV2'
import { ProductCardSmall } from '@/features/priceCalculator/PurchaseFormV2/OfferPresenterV2/ProductCardSmall/ProductCardSmall'
import { ProductTierSelectorV2 } from '@/features/priceCalculator/PurchaseFormV2/OfferPresenterV2/ProductTierSelectorV2/ProductTierSelectorV2'
import { BankSigneringEvent } from '@/services/bankSignering'
import { ExternalInsuranceCancellationOption } from '@/services/graphql/generated'
import { priceIntentServiceInitClientSide } from '@/services/priceIntent/PriceIntentService'
import {
useShopSessionIdOrThrow,
useShopSessionValueOrThrow,
Expand Down Expand Up @@ -163,10 +163,7 @@ function OfferSummary() {
const shopSessionId = useShopSessionIdOrThrow()
const selectedOffer = useSelectedOfferValueOrThrow()
const priceIntent = usePriceIntent()
const apolloClient = useApolloClient()
const priceTemplate = usePriceTemplate()

const setPriceCalculatorStep = useSetAtom(priceCalculatorStepAtom)
const setShowPurchaseSummary = useSetAtom(priceCalculatorShowPurchaseSummaryAtom)

const entryToReplace = useCartEntryToReplace()
const tracking = useTracking()
Expand Down Expand Up @@ -196,19 +193,9 @@ function OfferSummary() {
const handleAddToCart: MouseEventHandler = async (event) => {
event.preventDefault()
await addToCart(selectedOffer.id)
setPriceCalculatorStep('purchaseSummary')
// Make sure user views "added to cart" notification and/or bundle discount banner
window.scrollTo({ top: 0, behavior: 'instant' })
// Creates a new price intent but NOT synchronizing it with the price intent atoms like
// useResetPriceIntent does. By doing that user can see 'purchase summary' at the end of
// the flow while still can load price calculator for that product again by refreshing the
// page or navigating back.
const priceIntentService = priceIntentServiceInitClientSide(apolloClient)
priceIntentService.create({
shopSessionId,
productName: selectedOffer.product.name,
priceTemplate,
})
setShowPurchaseSummary(true)
}

const productData = useProductData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,3 @@ export const priceLoaderWrapper = style([
])

export const viewOffersWrapper = style([yStack(), centered, { gap: '2.75rem' }])

export const purchaseSummaryWrapper = style([
yStack({ alignItems: 'center', justifyContent: 'center' }),
{ height: '100%' },
])

export const purchaseSummary = style({
width: `min(100%, ${CONTENT_MAX_WIDTH})`,
marginInline: 'auto',
...responsiveStyles({
lg: {
width: `max(100%, ${CONTENT_MAX_WIDTH})`,
},
}),
})
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,10 @@ import {
} from '@/features/priceCalculator/priceCalculatorAtoms'
import { OfferPresenterV2 } from '@/features/priceCalculator/PurchaseFormV2/OfferPresenterV2/OfferPresenterV2'
import { InsuranceDataForm } from './InsuranceDataForm/InsuranceDataForm'
import {
centered,
priceLoaderWrapper,
viewOffersWrapper,
purchaseSummaryWrapper,
purchaseSummary,
} from './PurchaseFormV2.css'
import { PurchaseSummary } from './PurchaseSummary/PurchaseSummary'
import { centered, priceLoaderWrapper, viewOffersWrapper } from './PurchaseFormV2.css'

export function PurchaseFormV2() {
useSyncPriceIntentState()
useSyncPriceIntentState({ replacePriceIntentWhenCurrentIsAddedToCart: true })
useWarnOnPreloadedPriceIntentId()

const isReady = useIsPriceIntentStateReady()
Expand Down Expand Up @@ -57,12 +50,7 @@ export function PurchaseFormV2() {
<OfferPresenterV2 />
</div>
)
case 'purchaseSummary':
return (
<div className={purchaseSummaryWrapper}>
<PurchaseSummary className={purchaseSummary} />
</div>
)

default:
throw new Error(`Unexpected step: ${step}`)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import clsx from 'clsx'
import { useSetAtom } from 'jotai'
import { useTranslation } from 'next-i18next'
import { useCallback, useEffect } from 'react'
import { useEffect } from 'react'
import { Text, Card, Divider, Button, yStack } from 'ui'
import type { Banner } from '@/components/Banner/Banner.types'
import { ButtonNextLink } from '@/components/ButtonNextLink'
import { useSetGlobalBanner, useDismissBanner } from '@/components/GlobalBanner/globalBannerState'
import { Pillow } from '@/components/Pillow/Pillow'
import { useResetPriceIntent } from '@/components/ProductPage/PurchaseForm/priceIntentAtoms'
import { usePriceTemplate } from '@/components/ProductPage/PurchaseForm/priceTemplateAtom'
import { useSelectedOfferValueOrThrow } from '@/components/ProductPage/PurchaseForm/useSelectedOffer'
import { TextWithLink } from '@/components/TextWithLink'
Expand All @@ -16,6 +16,7 @@ import {
hasBundleDiscount,
hasCartItemsEligibleForBundleDiscount,
} from '@/features/bundleDiscount/bundleDiscount.utils'
import { priceCalculatorShowPurchaseSummaryAtom } from '@/features/priceCalculator/priceCalculatorAtoms'
import type { TemplateV2 } from '@/services/PriceCalculator/PriceCalculator.types'
import { useShopSessionValueOrThrow } from '@/services/shopSession/ShopSessionContext'
import { getOfferPrice } from '@/utils/getOfferPrice'
Expand All @@ -26,15 +27,20 @@ import { actions } from './PurchaseSummary.css'
export function PurchaseSummary({ className }: { className?: string }) {
const { t } = useTranslation('purchase-form')
const locale = useRoutingLocale()
// Selected offer is not price intent dependent but defined at page level. So even
// though at this point a new price intent is loaded the selected offer will remain
// referencing to an offer created by the previous price intent
const offer = useSelectedOfferValueOrThrow()
const priceTemplate = usePriceTemplate() as TemplateV2
const resetPriceIntent = useResetPriceIntent()
const setShowPurchaseSummary = useSetAtom(priceCalculatorShowPurchaseSummaryAtom)

useNotifyAboutBundleDiscounts()

const handleAddMore = useCallback(() => {
resetPriceIntent()
}, [resetPriceIntent])
const handleAddMore = () => {
// Price intent is reset whenever a product is added to the cart so the only thing
// we need to do in order to add another product is to close the purchase summary
setShowPurchaseSummary(false)
}

const showAddMoreButton = offer.product.multiple && priceTemplate.addMultiple

Expand Down Expand Up @@ -64,7 +70,7 @@ export function PurchaseSummary({ className }: { className?: string }) {
})}
</Button>
)}
<ButtonNextLink href={PageLink.checkout({ locale })} variant="primary">
<ButtonNextLink href={PageLink.checkout({ locale }).toRelative()} variant="primary">
{t('GO_TO_CART_LABEL')}
</ButtonNextLink>
<ButtonNextLink href={PageLink.store({ locale })} variant="ghost">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ import {
} from '@/components/ProductPage/PurchaseForm/priceIntentAtoms'
import { getAtomValueOrThrow } from '@/utils/jotaiUtils'

type PriceCalculatorStep =
| 'loadingForm'
| 'fillForm'
| 'calculatingPrice'
| 'viewOffers'
| 'purchaseSummary'
type PriceCalculatorStep = 'loadingForm' | 'fillForm' | 'calculatingPrice' | 'viewOffers'

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const priceCalculatorStepAtomFamily = atomFamily((priceIntentId: string) =>
Expand Down Expand Up @@ -57,3 +52,5 @@ export const usePriceCalculatorDeductibleInfo = () => {
}

export const priceCalculatorShowEditSsnWarningAtom = atom(false)

export const priceCalculatorShowPurchaseSummaryAtom = atom(false)

0 comments on commit 3ea0a2e

Please sign in to comment.