Skip to content

Commit

Permalink
GEN-748 | Add CRM Retargeting Landing Page (#2741)
Browse files Browse the repository at this point in the history
<!--
PR title: GRW-123 / Feature / Awesome new thing
-->

## Describe your changes

![Screenshot 2023-07-11 at 11.50.36.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/OgXegTwxM9IeuXHZyw5h/eb737c5f-f85b-40c4-bbf5-cadf87bced1a/Screenshot%202023-07-11%20at%2011.50.36.png)

- Implement a draft version of the CRM landing page to be used for re-targeting

- List all price intents that have been confirmed but not yet added to cart

<!--
What changes are made?
If there are many changes, a list might be a good format.
If it makes sense, add screenshots and/or screen recordings here.
-->

## Justify why they are needed

Design is just a draft but this is necessary to understand if we need to make any changes to the overall idea and/or API.

## Checklist before requesting a review

- [ ] I have performed a self-review of my code
  • Loading branch information
robinandeer authored Jul 13, 2023
1 parent d41a830 commit 1d30a97
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 0 deletions.
1 change: 1 addition & 0 deletions apps/store/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"COUNTRY_LABEL_NO": "Norway",
"COUNTRY_LABEL_SE": "Sweden",
"COUNTRY_NOT_DETECTED": "We’re not quite sure where you’re visiting us from. Select your country below.",
"CRM_RETARGETING_CHOOSE_TIER": "Choose coverage level",
"DATE_INPUT_EMPTY_LABEL": "Select date",
"DATE_SAME_DAY": "today",
"DATE_TOO_EARLY": "Date can't be earlier than {{minValue}}",
Expand Down
1 change: 1 addition & 0 deletions apps/store/public/locales/sv-se/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"COUNTRY_LABEL_DK": "Danmark",
"COUNTRY_LABEL_NO": "Norge",
"COUNTRY_LABEL_SE": "Sverige",
"CRM_RETARGETING_CHOOSE_TIER": "Välj skyddsnivå",
"DATE_INPUT_EMPTY_LABEL": "Välj...",
"DATE_SAME_DAY": "idag",
"DATE_TOO_EARLY": "Datum kan inte vara tidigare än {{minValue}}",
Expand Down
30 changes: 30 additions & 0 deletions apps/store/src/components/RetargetingPage/MultiTierOffer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useTranslation } from 'next-i18next'
import { CartItem } from '@/components/CartItem/CartItem'
import { type ProductOfferFragment } from '@/services/apollo/generated'
import { ProductPageLink } from './ProductPageLink'

type Props = {
product: ProductOfferFragment['variant']['product']
offers: Array<ProductOfferFragment>
defaultOffer: ProductOfferFragment
}

export const MultiTierOffer = (props: Props) => {
const { t } = useTranslation()

return (
<CartItem
pillow={props.product.pillowImage}
displayName={props.product.displayNameFull}
cost={props.defaultOffer.cost}
documents={props.defaultOffer.variant.documents}
productName={props.product.name}
data={props.defaultOffer.priceIntentData}
startDate={props.defaultOffer.startDate ? new Date(props.defaultOffer.startDate) : undefined}
>
<ProductPageLink href={props.product.pageLink}>
{t('CRM_RETARGETING_CHOOSE_TIER')}
</ProductPageLink>
</CartItem>
)
}
17 changes: 17 additions & 0 deletions apps/store/src/components/RetargetingPage/ProductPageLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ButtonNextLink } from '@/components/ButtonNextLink'
import { SpaceFlex } from '@/components/SpaceFlex/SpaceFlex'

type Props = {
href: string
children: string
}

export const ProductPageLink = (props: Props) => {
return (
<ButtonNextLink href={props.href} size="medium" variant="secondary-alt">
<SpaceFlex space={0.5} align="center">
{props.children}
</SpaceFlex>
</ButtonNextLink>
)
}
82 changes: 82 additions & 0 deletions apps/store/src/components/RetargetingPage/RetargetingPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import styled from '@emotion/styled'
import { type ComponentProps, useMemo } from 'react'
import { Heading, Space, theme } from 'ui'
import { GridLayout } from '@/components/GridLayout/GridLayout'
import { usePriceIntentsQuery } from '@/services/apollo/generated'
import { MultiTierOffer } from './MultiTierOffer'
import { SingleTierOffer } from './SingleTierOffer'

type Props = {
shopSessionId: string
}

type OfferSingle = ComponentProps<typeof SingleTierOffer> & { type: 'single' }
type OfferMultiple = ComponentProps<typeof MultiTierOffer> & { type: 'multiple' }
type Offer = (OfferSingle | OfferMultiple) & { key: string }

export const RetargetingPage = (props: Props) => {
const result = usePriceIntentsQuery({ variables: { shopSessionId: props.shopSessionId } })

const offers = useMemo(() => {
if (!result.data) return []

const cartOffers = new Set(result.data.shopSession.cart.entries.map((item) => item.id))

return result.data.shopSession.priceIntents.reduce<Array<Offer>>((total, item) => {
if (item.offers.some((offer) => cartOffers.has(offer.id))) {
return total
}

if (item.offers.length === 1) {
total.push({
key: item.id,
type: 'single',
product: item.offers[0].variant.product,
offer: item.offers[0],
})
} else if (item.offers.length > 1) {
total.push({
key: item.id,
type: 'multiple',
product: item.offers[0].variant.product,
defaultOffer: item.offers[0],
offers: item.offers,
})
}

return total
}, [])
}, [result.data])

return (
<GridLayout.Root>
<GridLayoutContent width={{ md: '2/3', lg: '1/2', xl: '1/3' }} align="center">
<Space y={2}>
<Heading as="h1" variant={{ _: 'serif.40' }} align="center">
Your current offers
</Heading>

<List>
{offers.map((item) => (
<li key={item.key}>
{item.type === 'single' && <SingleTierOffer {...item} />}
{item.type === 'multiple' && <MultiTierOffer {...item} />}
</li>
))}
</List>
</Space>
</GridLayoutContent>
</GridLayout.Root>
)
}

const GridLayoutContent = styled(GridLayout.Content)({
paddingBlock: theme.space.lg,
minHeight: '60vh',
})

const List = styled.ul({
display: 'flex',
flexDirection: 'column',
gap: theme.space.md,
})
42 changes: 42 additions & 0 deletions apps/store/src/components/RetargetingPage/SingleTierOffer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styled from '@emotion/styled'
import { useTranslation } from 'next-i18next'
import { Button, theme } from 'ui'
import { CartItem } from '@/components/CartItem/CartItem'
import { type ProductOfferFragment } from '@/services/apollo/generated'
import { ProductPageLink } from './ProductPageLink'

type Props = {
offer: ProductOfferFragment
product: ProductOfferFragment['variant']['product']
}

export const SingleTierOffer = (props: Props) => {
const { t } = useTranslation('cart')

return (
<CartItem
pillow={props.product.pillowImage}
displayName={props.product.displayNameFull}
cost={props.offer.cost}
documents={props.offer.variant.documents}
productName={props.product.name}
data={props.offer.priceIntentData}
startDate={props.offer.startDate ? new Date(props.offer.startDate) : undefined}
>
<ButtonGroup>
<ProductPageLink href={props.product.pageLink}>
{t('CART_ENTRY_EDIT_BUTTON')}
</ProductPageLink>
<Button size="medium" variant="secondary-alt">
{t('ADD_TO_CART_BUTTON_LABEL')}
</Button>
</ButtonGroup>
</CartItem>
)
}

const ButtonGroup = styled.div({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
columnGap: theme.space.xs,
})
16 changes: 16 additions & 0 deletions apps/store/src/graphql/PriceIntents.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
query PriceIntents($shopSessionId: UUID!) {
shopSession(id: $shopSessionId) {
id
cart {
entries {
id
}
}
priceIntents {
id
offers {
...ProductOffer
}
}
}
}
1 change: 1 addition & 0 deletions apps/store/src/graphql/ProductOfferFragment.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fragment ProductOffer on ProductOffer {
product {
id
name
pageLink
displayNameFull
pillowImage {
id
Expand Down
31 changes: 31 additions & 0 deletions apps/store/src/pages/session/resume/[shopSessionId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type GetServerSideProps, type NextPageWithLayout } from 'next'
import { getLayoutWithMenuProps } from '@/components/LayoutWithMenu/getLayoutWithMenuProps'
import { LayoutWithMenu } from '@/components/LayoutWithMenu/LayoutWithMenu'
import { RetargetingPage } from '@/components/RetargetingPage/RetargetingPage'

type Props = {
shopSessionId: string
}

type Params = {
shopSessionId: string
}

const NextPage: NextPageWithLayout<Props> = RetargetingPage

export const getServerSideProps: GetServerSideProps<Props, Params> = async (context) => {
if (!context.params) throw new Error('No params in context')

const { shopSessionId } = context.params

return {
props: {
...(await getLayoutWithMenuProps(context)),
shopSessionId,
},
}
}

NextPage.getLayout = (children) => <LayoutWithMenu>{children}</LayoutWithMenu>

export default NextPage

0 comments on commit 1d30a97

Please sign in to comment.