Skip to content

Commit

Permalink
Update to Insurely Blocks API
Browse files Browse the repository at this point in the history
  • Loading branch information
robinandeer committed Jun 7, 2023
1 parent 95e1c8e commit 13a3df0
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 77 deletions.
3 changes: 3 additions & 0 deletions apps/store/.env.local.example
Expand Up @@ -52,3 +52,6 @@ EDGE_CONFIG_MANAGE_API_TOKEN=:token
FALLBACK_ORIGIN_URL=https://www.dev.hedvigit.com

NEXT_PUBLIC_EXPERIMENT_ID=<experiment-id>

NEXT_PUBLIC_CUSTOMER_FIRST_SCRIPT=
NEXT_PUBLIC_INSURELY_CUSTOMER_ID=
39 changes: 25 additions & 14 deletions apps/store/index.d.ts
Expand Up @@ -34,31 +34,42 @@ type MerchantCallbackFunction = (data: MerchantCallbackData) => void
type MerchantCallbackData = { accountId: string }
type TrustlyOptions = { locale: string }

type InsurelyPrefilledInput = {
company: string
SWEDISH_PERSONAL_NUMBER: string
}
type InsurelyClientParams = {
fontType?: 'main' | 'secondary'
hideResultsView?: boolean
language?: 'en' | 'no' | 'sv' | 'da'
prefilledInput?: Partial<InsurelyPrefilledInput>
}

declare global {
interface Window {
TrustlyWidget: {
init: TrustlyInitFunction
}
dataLayer: DataLayerObject[]

// Insurely
setClientParams?: (params: InsurelyClientParams) => void

// Customer First (C1)
customerFirstAPI?: {
openWidget(): void
closeWidget(): void
}

// Insurely
insurely?: {
config?: InsurelyConfig
prefill?: InsurelyPrefill
}
}
}

type InsurelyConfig = {
customerId?: string
configName?: string
showCloseButton?: boolean
language?: 'en' | 'no' | 'sv' | 'da'
dataAggregation?: {
hideResultsView?: boolean
}
}

type InsurelyPrefill = {
user?: {
swedishPersonalNumber?: string
}
dataAggregation?: {
company?: string
}
}
7 changes: 6 additions & 1 deletion apps/store/next-csp.config.js
Expand Up @@ -34,7 +34,10 @@ const scriptSrc = [
'http://widget.trustpilot.com',
'https://widget.trustpilot.com',

// Insurely
'https://blocks.insurely.com',
'https://dc.insurely.com',

'https://vercel.live',
"'unsafe-inline'",
"'unsafe-eval'",
Expand Down Expand Up @@ -117,7 +120,6 @@ const connectSrc = [
"'self'",
]
const frameSrc = [
'https://dc.insurely.com',
'https://player.vimeo.com',
'https://vercel.live', // Vercel Live
'https://www.googletagmanager.com',
Expand All @@ -140,6 +142,9 @@ const frameSrc = [
// Google Optimize
'https://optimize.google.com',

// Insurely
'https://blocks.insurely.com',

"'self'",
]

Expand Down
3 changes: 2 additions & 1 deletion apps/store/src/components/PriceCalculator/AutomaticField.tsx
Expand Up @@ -150,7 +150,8 @@ export const AutomaticField = ({ field, priceIntent, onSubmit, loading, autoFocu
label={translateLabel(field.label)}
productName={productData.name}
priceIntentId={priceIntent.id}
insurelyClientId={productData.insurelyClientId ?? undefined}
// TODO: Switch to Insurely config name when we have it
insurelyConfigName={productData.insurelyClientId ?? undefined}
externalInsurer={priceIntent.externalInsurer?.id}
/>
)
Expand Down
Expand Up @@ -11,7 +11,7 @@ import {
ExternalInsurer,
useInsurelyDataCollectionCreateMutation,
} from '@/services/apollo/generated'
import { InsurelyIframe, insurelyPrefillInput } from '@/services/Insurely/Insurely'
import { InsurelyIframe, setInsurelyConfig } from '@/services/Insurely/Insurely'
import {
INSURELY_IFRAME_MAX_HEIGHT,
INSURELY_IFRAME_MAX_WIDTH,
Expand All @@ -27,25 +27,24 @@ type Props = {
label: string
productName: string
priceIntentId: string
insurelyClientId?: string
insurelyConfigName?: string
externalInsurer?: string
}

type State =
| { type: 'IDLE' }
| { type: 'COMPARE'; externalInsurer: ExternalInsurer; insurelyClientId: string }
| { type: 'COMPARE'; externalInsurer: ExternalInsurer; insurelyConfigName: string }
| { type: 'SUCCESS'; externalInsurer: ExternalInsurer }
| { type: 'CONFIRMED'; externalInsurer: ExternalInsurer }

export const CurrentInsuranceField = (props: Props) => {
const { label, productName, priceIntentId, insurelyClientId, externalInsurer } = props
const { t } = useTranslation('purchase-form')
const { shopSession } = useShopSession()
const [state, setState] = useState<State>({ type: 'IDLE' })

const compare = useCallback(
(externalInsurer: ExternalInsurer, insurelyClientId: string) =>
setState({ type: 'COMPARE', externalInsurer, insurelyClientId }),
(externalInsurer: ExternalInsurer, insurelyConfigName: string) =>
setState({ type: 'COMPARE', externalInsurer, insurelyConfigName }),
[],
)
const close = useCallback(() => setState({ type: 'IDLE' }), [])
Expand All @@ -59,21 +58,26 @@ export const CurrentInsuranceField = (props: Props) => {
)
const isDialogOpen = INSURELY_IS_ENABLED && (state.type === 'COMPARE' || state.type === 'SUCCESS')

const companyOptions = useCompanyOptions(productName)
const companyOptions = useCompanyOptions(props.productName)
const updateExternalInsurer = useUpdateExternalInsurer({
priceIntentId,
priceIntentId: props.priceIntentId,
onCompleted(updatedPriceIntent) {
const externalInsurer = updatedPriceIntent.externalInsurer
const personalNumber = shopSession?.customer?.ssn
const ssn = shopSession?.customer?.ssn

if (!externalInsurer) {
datadogLogs.logger.warn('Failed to update external insurer', { priceIntentId })
datadogLogs.logger.warn('Failed to update external insurer', {
priceIntentId: props.priceIntentId,
})
return
}

if (insurelyClientId && personalNumber) {
compare(externalInsurer, insurelyClientId)
insurelyPrefillInput({ company: externalInsurer.insurelyId ?? undefined, personalNumber })
if (props.insurelyConfigName && ssn) {
compare(externalInsurer, props.insurelyConfigName)
setInsurelyConfig({
company: externalInsurer.insurelyId ?? undefined,
ssn,
})
} else {
confirm(externalInsurer)
}
Expand All @@ -88,7 +92,7 @@ export const CurrentInsuranceField = (props: Props) => {
const [updateDataCollectionId] = usePriceIntentInsurelyUpdateMutation({
onCompleted({ priceIntentInsurelyUpdate }) {
datadogLogs.logger.info('Updated Insurely data collection ID', {
priceIntentId,
priceIntentId: props.priceIntentId,
dataCollectionId,
})

Expand All @@ -97,7 +101,7 @@ export const CurrentInsuranceField = (props: Props) => {
success(updatedPriceIntent.externalInsurer)
} else {
datadogLogs.logger.error('Failed to update Insurely data collection ID', {
priceIntentId,
priceIntentId: props.priceIntentId,
dataCollectionId,
})
}
Expand All @@ -115,13 +119,13 @@ export const CurrentInsuranceField = (props: Props) => {
setDataCollectionId(dataCollectionId)
} else {
datadogLogs.logger.error('Failed to create Insurely data collection', {
priceIntentId,
priceIntentId: props.priceIntentId,
})
}
},
onError(error) {
datadogLogs.logger.error('Error creating Insurely data collection', {
priceIntentId,
priceIntentId: props.priceIntentId,
error,
})
},
Expand All @@ -133,19 +137,21 @@ export const CurrentInsuranceField = (props: Props) => {

const handleInsurelyCompleted = useCallback(() => {
if (dataCollectionId) {
updateDataCollectionId({ variables: { priceIntentId, dataCollectionId } })
updateDataCollectionId({
variables: { priceIntentId: props.priceIntentId, dataCollectionId },
})
} else {
datadogLogs.logger.error('Completed Insurely without creating data collection ID', {
priceIntentId,
priceIntentId: props.priceIntentId,
})
}
}, [updateDataCollectionId, priceIntentId, dataCollectionId])
}, [updateDataCollectionId, props.priceIntentId, dataCollectionId])

return (
<>
<InputCurrentInsurance
label={label}
company={externalInsurer}
label={props.label}
company={props.externalInsurer}
companyOptions={companyOptions}
onCompanyChange={handleCompanyChange}
/>
Expand All @@ -155,7 +161,7 @@ export const CurrentInsuranceField = (props: Props) => {
{state.type === 'COMPARE' ? (
<DialogIframeWindow>
<InsurelyIframe
clientId={state.insurelyClientId}
configName={state.insurelyConfigName}
onCollection={handleInsurelyCollection}
onClose={close}
onCompleted={handleInsurelyCompleted}
Expand Down
83 changes: 45 additions & 38 deletions apps/store/src/services/Insurely/Insurely.tsx
Expand Up @@ -2,12 +2,13 @@

import styled from '@emotion/styled'
import Script from 'next/script'
import { useEffect, useMemo } from 'react'
import { useEffect } from 'react'
import { Language } from '@/utils/l10n/types'
import { useCurrentLocale } from '@/utils/l10n/useCurrentLocale'

const IFRAME_URL = 'https://dc.insurely.com/v2/select-authentication'
const BOOTSTRAP_SCRIPT_URL = 'https://dc.insurely.com/v2/assets/js/dc-bootstrap.js'
const PREFILL_STORE: Array<Record<string, string>> = []
const CUSTOMER_ID = process.env.NEXT_PUBLIC_INSURELY_CUSTOMER_ID as string
const IFRAME_URL = 'https://blocks.insurely.com/'
const BOOTSTRAP_SCRIPT_URL = 'https://blocks.insurely.com/assets/bootstrap.js'

enum EventName {
APP_LOADED = 'APP_LOADED',
Expand All @@ -24,17 +25,15 @@ type InsurelyMessage =
| { name: EventName.RESULTS }

type InsurelyIframeProps = {
clientId: string
configName: string
onLoaded?: () => void
onClose?: () => void
onCollection?: (collectionId: string) => void
onCompleted?: () => void
}

export const InsurelyIframe = (props: InsurelyIframeProps) => {
const { clientId, onLoaded, onClose, onCollection, onCompleted } = props
const { language } = useCurrentLocale()

const { onLoaded, onClose, onCollection, onCompleted } = props
useEffect(() => {
const handleMessage = ({ data }: MessageEvent<InsurelyMessage>) => {
switch (data.name) {
Expand All @@ -55,26 +54,17 @@ export const InsurelyIframe = (props: InsurelyIframeProps) => {
return () => window.removeEventListener('message', handleMessage)
}, [onLoaded, onClose, onCollection, onCompleted])

const handleLoad = () => {
const prefilledInput = PREFILL_STORE.shift()
window.setClientParams?.({
fontType: 'secondary',
hideResultsView: true,
language,
...(prefilledInput && { prefilledInput }),
})
}

const iFrameSrc = useMemo(() => {
const queryParams = new URLSearchParams({ clientId })
return `${IFRAME_URL}?${queryParams.toString()}`
}, [clientId])
const { language } = useCurrentLocale()
useEffect(() => {
setInsurelyConfig({ customerId: CUSTOMER_ID, configName: props.configName, language })
}, [language, props.configName])

return (
<>
<StyledIframe
title="Insurely"
src={iFrameSrc}
id="insurely-data-aggregation"
title="insurely-data-aggregation"
src={IFRAME_URL}
sandbox="allow-scripts
allow-same-origin
allow-popups
Expand All @@ -83,26 +73,43 @@ export const InsurelyIframe = (props: InsurelyIframeProps) => {
allow-top-navigation"
/>

<Script strategy="afterInteractive" src={BOOTSTRAP_SCRIPT_URL} onLoad={handleLoad} />
<Script strategy="afterInteractive" src={BOOTSTRAP_SCRIPT_URL} />
</>
)
}

type InsurelyClientParams = Partial<{ company: string; personalNumber: string }>

export const insurelyPrefillInput = ({ personalNumber, company }: InsurelyClientParams) => {
const prefilledInput = {
...(personalNumber && { SWEDISH_PERSONAL_NUMBER: personalNumber }),
...(company && { company }),
}
type InsurelyConfig = {
customerId?: string
configName?: string
company?: string
ssn?: string
language?: Language
}

// If the iframe is already loaded, we can set the prefilled input directly
if (window.setClientParams) {
return window.setClientParams({ prefilledInput })
export const setInsurelyConfig = (config: InsurelyConfig) => {
window.insurely = {
config: {
...window.insurely?.config,
...(config.customerId && { customerId: config.customerId }),
...(config.configName && { configName: config.configName }),
...(config.language && { language: config.language }),

// showCloseButton: true,
dataAggregation: {
hideResultsView: true,
},
},
prefill: {
dataAggregation: {
...window.insurely?.prefill?.dataAggregation,
...(config.company && { company: config.company }),
},
user: {
...window.insurely?.prefill?.user,
...(config.ssn && { swedishPersonalNumber: config.ssn }),
},
},
}

// Otherwise we store the prefilled input and set it when the iframe is loaded
PREFILL_STORE.push(prefilledInput)
}

const StyledIframe = styled.iframe({ border: 0 })

0 comments on commit 13a3df0

Please sign in to comment.