Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useEffect, useRef } from 'react'

import {
ConnectionBehavior,
deriveConnectionBehavior,
Expand Down Expand Up @@ -26,13 +28,15 @@ interface AdditionalIntegrationSettingsSelectorProps {
customerId: string
values: AdditionalIntegrationSettingsValues
onChange: (values: AdditionalIntegrationSettingsValues) => void
autoOpen?: boolean
'data-test'?: string
}

export const AdditionalIntegrationSettingsSelector = ({
customerId,
values,
onChange,
autoOpen = false,
'data-test': dataTest = ADDITIONAL_INTEGRATION_SETTINGS_SELECTOR_TEST_ID,
}: AdditionalIntegrationSettingsSelectorProps) => {
const { translate } = useInternationalization()
Expand All @@ -41,6 +45,16 @@ export const AdditionalIntegrationSettingsSelector = ({
onSave: onChange,
})

const hasAutoOpened = useRef(false)

useEffect(() => {
if (!autoOpen || hasAutoOpened.current) return

hasAutoOpened.current = true
openDrawer(values)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoOpen])

const getSubtitle = (): string => {
const segments = ADDITIONAL_INTEGRATION_CATEGORIES.reduce<string[]>((acc, category) => {
const behavior = deriveConnectionBehavior(values[category])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ describe('AdditionalIntegrationSettingsSelector', () => {
}
})

describe('GIVEN the details view asks for the drawer to open on landing', () => {
const EMPTY_VALUES = {
[ConnectionCategory.Accounting]: undefined,
[ConnectionCategory.Crm]: undefined,
[ConnectionCategory.Tax]: undefined,
}

describe('WHEN the selector mounts with autoOpen', () => {
it('THEN should open the drawer once', () => {
const { rerender } = render(
<AdditionalIntegrationSettingsSelector
customerId="customer-1"
values={EMPTY_VALUES}
onChange={jest.fn()}
autoOpen
/>,
)

expect(mockOpen).toHaveBeenCalledTimes(1)

rerender(
<AdditionalIntegrationSettingsSelector
customerId="customer-1"
values={EMPTY_VALUES}
onChange={jest.fn()}
autoOpen
/>,
)

expect(mockOpen).toHaveBeenCalledTimes(1)
})
})
})

describe('GIVEN the selector is mounted', () => {
describe('WHEN it renders', () => {
it('THEN should display the entry card without opening the drawer', () => {
Expand Down
30 changes: 30 additions & 0 deletions src/components/connectionSelection/ConnectionCodeChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ReactNode } from 'react'

import { Avatar } from '~/components/designSystem/Avatar'
import { Chip } from '~/components/designSystem/Chip'

type ConnectionCodeChipProps = {
code: string
avatar?: ReactNode
'data-test'?: string
}

export const ConnectionCodeChip = ({
code,
avatar,
'data-test': dataTest,
}: ConnectionCodeChipProps): JSX.Element => (
<Chip
data-test={dataTest}
label={
<span className="flex items-center gap-2">
{!!avatar && (
<Avatar size="small" variant="connector-full">
{avatar}
</Avatar>
)}
{code}
</span>
}
/>
)
150 changes: 150 additions & 0 deletions src/components/connectionSelection/ConnectionRoutingValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { ReactNode } from 'react'

import { integrationAvatarMapping, paymentAvatarMapping } from '~/components/avatarMappings'
import {
ConnectionCategory,
IntegrationConnectionCategory,
} from '~/components/customerConnections/types'
import { Typography } from '~/components/designSystem/Typography'
import { ConnectionResolvedBehaviorEnum } from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { useCustomerIntegrationConnections } from '~/hooks/customer/useCustomerIntegrationConnections'
import { useCustomerPaymentConnections } from '~/hooks/customer/useCustomerPaymentConnections'

import { ConnectionCodeChip } from './ConnectionCodeChip'

export const CONNECTION_ROUTING_CHIP_TEST_ID = 'connection-routing-chip'
export const CONNECTION_ROUTING_INHERITED_TEST_ID = 'connection-routing-inherited'
export const CONNECTION_ROUTING_SKIPPED_TEST_ID = 'connection-routing-skipped'
export const CONNECTION_ROUTING_UNRESOLVED_TEST_ID = 'connection-routing-unresolved'

export type ConnectionRoutingDisplay = {
behavior: ConnectionResolvedBehaviorEnum
code?: string | null
}

type ConnectionRoutingChipProps = {
routing?: ConnectionRoutingDisplay
avatar?: ReactNode
}

const ConnectionRoutingChip = ({ routing, avatar }: ConnectionRoutingChipProps): JSX.Element => {
const { translate } = useInternationalization()

if (routing?.behavior === ConnectionResolvedBehaviorEnum.Skip) {
return (
<Typography
data-test={CONNECTION_ROUTING_SKIPPED_TEST_ID}
variant="body"
color="grey700"
component="span"
>
{translate('text_1789472252793x3dxbqu3x10')}
</Typography>
)
}

if (!routing?.code) {
return (
<Typography
data-test={CONNECTION_ROUTING_UNRESOLVED_TEST_ID}
variant="body"
color="grey700"
component="span"
>
{translate('text_1789382180711vi1jj3immjw')}
</Typography>
)
}

return (
<span className="flex items-center gap-2">
<ConnectionCodeChip
code={routing.code}
avatar={avatar}
data-test={CONNECTION_ROUTING_CHIP_TEST_ID}
/>

{routing.behavior === ConnectionResolvedBehaviorEnum.Inherit && (
<Typography
data-test={CONNECTION_ROUTING_INHERITED_TEST_ID}
variant="body"
color="grey600"
component="span"
>
{`(${translate('text_1789558944658bay5bstkcut')})`}
</Typography>
)}
</span>
)
}

const PaymentConnectionRoutingValue = ({
customerId,
routing,
}: {
customerId?: string
routing?: ConnectionRoutingDisplay
}): JSX.Element => {
const { connections } = useCustomerPaymentConnections({ customerId, skip: !routing?.code })

const provider = connections.find((connection) => connection.code === routing?.code)?.provider

return (
<ConnectionRoutingChip
routing={routing}
avatar={provider ? paymentAvatarMapping[provider] : undefined}
/>
)
}

const IntegrationConnectionRoutingValue = ({
category,
customerId,
routing,
}: {
category: IntegrationConnectionCategory
customerId?: string
routing?: ConnectionRoutingDisplay
}): JSX.Element => {
const { connections } = useCustomerIntegrationConnections({
customerId,
category,
skip: !routing?.code,
})

const integrationType = connections.find(
(connection) => connection.code === routing?.code,
)?.integrationType

return (
<ConnectionRoutingChip
routing={routing}
avatar={integrationType ? integrationAvatarMapping[integrationType] : undefined}
/>
)
}

type ConnectionRoutingValueProps = {
category: ConnectionCategory
customerId?: string
routing?: ConnectionRoutingDisplay
}

export const ConnectionRoutingValue = ({
category,
customerId,
routing,
}: ConnectionRoutingValueProps): JSX.Element => {
if (category === ConnectionCategory.Payment) {
return <PaymentConnectionRoutingValue customerId={customerId} routing={routing} />
}

return (
<IntegrationConnectionRoutingValue
category={category}
customerId={customerId}
routing={routing}
/>
)
}
134 changes: 134 additions & 0 deletions src/components/connectionSelection/ConnectionSettingsSections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { ReactNode } from 'react'

import { ADDITIONAL_INTEGRATION_CATEGORIES } from '~/components/additionalIntegrationSettings/additionalIntegrationSettingsSchema'
import { findConnectionRouting } from '~/components/connectionSelection/fromConnectionRouting'
import { ConnectionCategory } from '~/components/customerConnections/types'
import { Typography } from '~/components/designSystem/Typography'
import { DetailsPage } from '~/components/layouts/DetailsPage'
import { SelectedPaymentMethod } from '~/components/paymentMethodSelection/types'
import { ConnectionCategoryEnum } from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'

import { PaymentMethodValue } from './PaymentMethodValue'
import {
ConnectionRoutingGridItem,
ConnectionRoutingRow,
useConnectionRoutingGridItems,
} from './useConnectionRoutingGridItems'

export const CONNECTION_SETTINGS_PAYMENT_SECTION_TEST_ID = 'connection-settings-payment-section'
export const CONNECTION_SETTINGS_ADDITIONAL_SECTION_TEST_ID =
'connection-settings-additional-section'

type SectionHeaderProps = {
title: string
description: string
action?: ReactNode
}

const SectionHeader = ({ title, description, action }: SectionHeaderProps): JSX.Element => (
<div className="flex items-start justify-between gap-4">
<div className="flex flex-col">
<Typography variant="bodyHl" color="grey700">
{title}
</Typography>

<Typography variant="caption" color="grey600">
{description}
</Typography>
</div>

{!!action && action}
</div>
)

type ConnectionSettingsSectionsProps = {
connections?: ConnectionRoutingRow[] | null
customerId?: string
externalCustomerId?: string
selectedPaymentMethod?: SelectedPaymentMethod
paymentDescription: string
additionalDescription: string
paymentAction?: ReactNode
additionalAction?: ReactNode
extraPaymentItems?: ConnectionRoutingGridItem[]
}

export const ConnectionSettingsSections = ({
connections,
customerId,
externalCustomerId,
selectedPaymentMethod,
paymentDescription,
additionalDescription,
paymentAction,
additionalAction,
extraPaymentItems = [],
}: ConnectionSettingsSectionsProps): JSX.Element => {
const { translate } = useInternationalization()

const paymentConnectionItems = useConnectionRoutingGridItems({
categories: [ConnectionCategory.Payment],
connections,
customerId,
})

const additionalConnectionItems = useConnectionRoutingGridItems({
categories: ADDITIONAL_INTEGRATION_CATEGORIES,
connections,
customerId,
})

return (
<>
<section
data-test={CONNECTION_SETTINGS_PAYMENT_SECTION_TEST_ID}
className="flex flex-col gap-6 pb-12 shadow-b"
>
<SectionHeader
title={translate('text_1782825858647rr5zp42t63m')}
description={paymentDescription}
action={paymentAction}
/>

<DetailsPage.InfoGrid
grid={[
...paymentConnectionItems,
{
label: translate('text_1773043324341qj7t72i7qnk'),
value: (
<PaymentMethodValue
selectedPaymentMethod={selectedPaymentMethod}
externalCustomerId={externalCustomerId}
customerId={customerId}
paymentRouting={findConnectionRouting(
connections,
ConnectionCategoryEnum.Payment,
)}
/>
),
},
...extraPaymentItems,
]}
/>
</section>

<section
data-test={CONNECTION_SETTINGS_ADDITIONAL_SECTION_TEST_ID}
className="flex flex-col gap-6 pb-12"
>
<SectionHeader
title={translate('text_178955797239251hkkoh9sbb')}
description={additionalDescription}
action={additionalAction}
/>

<div className="flex flex-col gap-4">
{additionalConnectionItems.map((item) => (
<DetailsPage.InfoGridItem key={item.label} label={item.label} value={item.value} />
))}
</div>
</section>
</>
)
}
Loading
Loading