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

feat(CustomerRevamp): Refactor customer subscription list, customer coupons and customer details #1997

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c77bc2d
feat(icons): add arrow-indent icon
stephenlago99 Jan 21, 2025
3aa3685
chore(translations): add translations
stephenlago99 Jan 21, 2025
fdbec2c
chore(graphql): generate types
stephenlago99 Jan 21, 2025
76a8ff2
feat(TimezoneDate): add className prop
stephenlago99 Jan 21, 2025
317ff88
feat(Status): add support for downgrade and scheduled statuses
stephenlago99 Jan 21, 2025
b4ce221
feat(icons): add arrow-indent icon
stephenlago99 Jan 21, 2025
268452e
feat(layoutsSection): create reusable page title component
stephenlago99 Jan 21, 2025
226f2d1
feat(CouponCaption): add className prop
stephenlago99 Jan 21, 2025
95d921a
refactor(CustomerPage): unify section heights
stephenlago99 Jan 21, 2025
f8edf35
refactor(CustomerCoupons): list -> table
stephenlago99 Jan 21, 2025
201b02a
refactor(CustomerSubscriptionsList): list -> table
stephenlago99 Jan 21, 2025
981d065
refactor(CustomerMainInfos): always show all the customer details
stephenlago99 Jan 21, 2025
86f73a3
refactor(CustomerOverview): re-use PageSectionTitle
stephenlago99 Jan 21, 2025
e509605
refactor(CustomerDetails): replace customer details drawer with tab
stephenlago99 Jan 21, 2025
0e00e6e
fix: linting
stephenlago99 Jan 21, 2025
00b5440
fix(translations): remove unused translations
stephenlago99 Jan 22, 2025
bf64c08
feat(translations): add other languages
stephenlago99 Jan 22, 2025
f209a93
refactor(Chip): remove clsx dependency
stephenlago99 Jan 22, 2025
c9d2dd5
refactor(CustomerSubscriptionsList): remove unused fragment
stephenlago99 Jan 22, 2025
7c589c0
fix(Status): fix types
stephenlago99 Jan 22, 2025
23f0437
refactor(CustomerSubscriptionsList): add padding to rows
stephenlago99 Jan 22, 2025
85b3085
delete: remove SubscriptionLine
stephenlago99 Jan 22, 2025
63f777a
refactor(CustomerSubscriptionsList): update graphql
stephenlago99 Jan 22, 2025
18e215f
chore(graphql): generate types
stephenlago99 Jan 22, 2025
0791f3c
refactor(CustomerSubscriptionsList): update fragment for next subscri…
stephenlago99 Jan 22, 2025
366100c
chore(translations): add translations
stephenlago99 Jan 23, 2025
6af42cf
feat(CustomerDetails): update label
stephenlago99 Jan 23, 2025
011e63e
feat(TimezoneDate): add typographyClassName prop
stephenlago99 Jan 23, 2025
a137a26
refactor(CustomerSubscriptionsList): scheduled status. column order. …
stephenlago99 Jan 23, 2025
239ff0f
chore(graphql): generate types
stephenlago99 Jan 23, 2025
a3135d5
fix(CustomerSubscriptionList): enable clicking on downgrade. fix down…
stephenlago99 Jan 23, 2025
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: 5 additions & 2 deletions src/components/TimezoneDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { TimezoneEnum } from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { useOrganizationInfos } from '~/hooks/useOrganizationInfos'
import { tw } from '~/styles/utils'

enum MainTimezoneEnum {
utc0 = 'utc0',
Expand All @@ -15,8 +16,9 @@
mainDateFormat?: string
mainTimezone?: keyof typeof MainTimezoneEnum
customerTimezone?: TimezoneEnum
mainTypographyProps?: Pick<TypographyProps, 'variant' | 'color'>
mainTypographyProps?: Pick<TypographyProps, 'variant' | 'color' | 'className'>
className?: string
typographyClassName?: string
}

export const TimezoneDate = ({
Expand All @@ -25,6 +27,7 @@
mainTimezone = MainTimezoneEnum.organization,
customerTimezone,
mainTypographyProps,
typographyClassName,
className,
}: TimezoneDateProps) => {
const { translate } = useInternationalization()
Expand Down Expand Up @@ -69,14 +72,14 @@
placement="top-end"
>
<Typography
className="w-max border-b-2 border-dotted border-grey-400"
className={tw('w-max border-b-2 border-dotted border-grey-400', typographyClassName)}
color="grey700"
{...mainTypographyProps}
noWrap
>
{formatDateToTZ(
date,
mainTimezone === MainTimezoneEnum.organization

Check warning on line 82 in src/components/TimezoneDate.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Do not nest ternary expressions
? timezone
: mainTimezone === MainTimezoneEnum.customer
? customerTimezone
Expand Down
187 changes: 103 additions & 84 deletions src/components/coupons/CouponCaption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,104 +44,123 @@ export interface CouponMixedType extends CouponItemFragment {
interface CouponCaptionProps {
coupon: CouponMixedType
variant?: TypographyProps['variant']
className?: string
}

export const CouponCaption = memo(({ coupon, variant = 'caption' }: CouponCaptionProps) => {
const { translate } = useInternationalization()
export const CouponCaption = memo(
({ coupon, variant = 'caption', className }: CouponCaptionProps) => {
const { translate } = useInternationalization()

const getCaption = () => {
const {
amountCurrency,
amountCents,
amountCentsRemaining,
percentageRate,
frequency,
frequencyDuration,
frequencyDurationRemaining,
} = coupon
const couponType = amountCents ? CouponTypeEnum.FixedAmount : CouponTypeEnum.Percentage
const getCaption = () => {
const {
amountCurrency,
amountCents,
amountCentsRemaining,
percentageRate,
frequency,
frequencyDuration,
frequencyDurationRemaining,
} = coupon
const couponType = amountCents ? CouponTypeEnum.FixedAmount : CouponTypeEnum.Percentage

if (couponType === CouponTypeEnum.FixedAmount && frequency === CouponFrequency.Once) {
return translate(
amountCentsRemaining ? 'text_637b4da08cd0118cd0c4486f' : 'text_632d68358f1fedc68eed3e70',
{
amount: intlFormatNumber(
deserializeAmount(
Number(amountCentsRemaining) || Number(amountCents),
amountCurrency || CurrencyEnum.Usd,
) || 0,
{
currencyDisplay: 'symbol',
currency: amountCurrency || undefined,
},
),
},
)
} else if (couponType === CouponTypeEnum.Percentage && frequency === CouponFrequency.Once) {
return translate('text_632d68358f1fedc68eed3eb5', {
rate: intlFormatNumber(Number(percentageRate) / 100 || 0, {
style: 'percent',
}),
})
} else if (
couponType === CouponTypeEnum.FixedAmount &&
frequency === CouponFrequency.Recurring
) {
return translate(
'text_632d68358f1fedc68eed3ede',
{
if (couponType === CouponTypeEnum.FixedAmount && frequency === CouponFrequency.Once) {
return translate(
amountCentsRemaining ? 'text_637b4da08cd0118cd0c4486f' : 'text_632d68358f1fedc68eed3e70',
{
amount: intlFormatNumber(
deserializeAmount(
Number(amountCentsRemaining) || Number(amountCents),
amountCurrency || CurrencyEnum.Usd,
) || 0,
{
currencyDisplay: 'symbol',
currency: amountCurrency || undefined,
},
),
},
)
} else if (couponType === CouponTypeEnum.Percentage && frequency === CouponFrequency.Once) {
return translate('text_632d68358f1fedc68eed3eb5', {
rate: intlFormatNumber(Number(percentageRate) / 100 || 0, {
style: 'percent',
}),
})
} else if (
couponType === CouponTypeEnum.FixedAmount &&
frequency === CouponFrequency.Recurring
) {
return translate(
'text_632d68358f1fedc68eed3ede',
{
amount: intlFormatNumber(
deserializeAmount(
Number(amountCentsRemaining) || Number(amountCents),
amountCurrency || CurrencyEnum.Usd,
) || 0,
{
currencyDisplay: 'symbol',
currency: amountCurrency || undefined,
},
),
duration: frequencyDurationRemaining || frequencyDuration,
},
frequencyDurationRemaining || frequencyDuration || 1,
)
} else if (
couponType === CouponTypeEnum.Percentage &&
frequency === CouponFrequency.Recurring
) {
return translate(
'text_632d68358f1fedc68eed3ef9',
{
rate: intlFormatNumber(Number(percentageRate) / 100 || 0, {
style: 'percent',
}),
duration: frequencyDurationRemaining || frequencyDuration,
},
frequencyDurationRemaining || frequencyDuration || 1,
)
} else if (
couponType === CouponTypeEnum.FixedAmount &&
frequency === CouponFrequency.Forever
) {
return translate('text_63c946e8bef768ead2fee35c', {
amount: intlFormatNumber(
deserializeAmount(
Number(amountCentsRemaining) || Number(amountCents),
amountCurrency || CurrencyEnum.Usd,
) || 0,
deserializeAmount(Number(amountCents), amountCurrency || CurrencyEnum.Usd) || 0,
{
currencyDisplay: 'symbol',
currency: amountCurrency || undefined,
},
),
duration: frequencyDurationRemaining || frequencyDuration,
},
frequencyDurationRemaining || frequencyDuration || 1,
)
} else if (
couponType === CouponTypeEnum.Percentage &&
frequency === CouponFrequency.Recurring
) {
return translate(
'text_632d68358f1fedc68eed3ef9',
{
})
} else if (
couponType === CouponTypeEnum.Percentage &&
frequency === CouponFrequency.Forever
) {
return translate('text_63c96b18bfbf40e9ef600e99', {
rate: intlFormatNumber(Number(percentageRate) / 100 || 0, {
style: 'percent',
}),
duration: frequencyDurationRemaining || frequencyDuration,
},
frequencyDurationRemaining || frequencyDuration || 1,
)
} else if (couponType === CouponTypeEnum.FixedAmount && frequency === CouponFrequency.Forever) {
return translate('text_63c946e8bef768ead2fee35c', {
amount: intlFormatNumber(
deserializeAmount(Number(amountCents), amountCurrency || CurrencyEnum.Usd) || 0,
{
currencyDisplay: 'symbol',
currency: amountCurrency || undefined,
},
),
})
} else if (couponType === CouponTypeEnum.Percentage && frequency === CouponFrequency.Forever) {
return translate('text_63c96b18bfbf40e9ef600e99', {
rate: intlFormatNumber(Number(percentageRate) / 100 || 0, {
style: 'percent',
}),
})
})
}
}
}

return (
<Typography variant={variant} color="grey600" noWrap data-test="coupon-caption">
{getCaption()}
</Typography>
)
})
return (
<>
{!className && (
<Typography variant={variant} color="grey600" noWrap data-test="coupon-caption">
{getCaption()}
</Typography>
)}

{className && (
stephenlago99 marked this conversation as resolved.
Show resolved Hide resolved
<Typography className={className} data-test="coupon-caption">
{getCaption()}
</Typography>
)}
</>
)
},
)

CouponCaption.displayName = 'CouponCaption'
2 changes: 1 addition & 1 deletion src/components/customers/CustomerCreditNotesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const CustomerCreditNotesList = ({

return (
<div>
<SectionHeader variant="subhead" color="grey700" hideBottomShadow>
<SectionHeader className="h-auto pb-4" variant="subhead" color="grey700" hideBottomShadow>
{translate('text_63725b30957fd5b26b308dd7')}
</SectionHeader>
<div className="mb-8 flex h-18 items-center justify-between rounded-xl border border-grey-400 px-4 py-3">
Expand Down
2 changes: 1 addition & 1 deletion src/components/customers/CustomerInvoicesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const CustomerInvoicesTab = ({ customerId, customerTimezone }: CustomerIn
<>
{!!invoicesDraft?.length && (
<div className="mb-12">
<div className="flex h-18 items-center justify-between">
<div className="flex h-auto items-center justify-between pb-4">
<Typography variant="subhead" color="grey700">
{translate('text_638f4d756d899445f18a49ee')}
</Typography>
Expand Down
76 changes: 13 additions & 63 deletions src/components/customers/CustomerMainInfos.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { gql } from '@apollo/client'
import { Stack } from '@mui/material'
import { FC, PropsWithChildren, useCallback, useRef, useState } from 'react'
import { FC, PropsWithChildren } from 'react'
import { Link, LinkProps } from 'react-router-dom'
import styled from 'styled-components'

import { TRANSLATIONS_MAP_CUSTOMER_TYPE } from '~/components/customers/utils'
import { Avatar, Button, Icon, Skeleton, Typography } from '~/components/designSystem'
import { Avatar, Icon, Skeleton, Typography } from '~/components/designSystem'
import { PageSectionTitle } from '~/components/layouts/Section'
import { CountryCodes } from '~/core/constants/countryCodes'
import {
buildAnrokCustomerUrl,
Expand Down Expand Up @@ -195,8 +196,6 @@ interface CustomerMainInfosProps {
onEdit?: () => unknown
}

const SHOW_MORE_THRESHOLD = 6

const InlineLink: FC<PropsWithChildren<LinkProps>> = ({ children, ...props }) => {
return (
<Link
Expand All @@ -210,9 +209,6 @@ const InlineLink: FC<PropsWithChildren<LinkProps>> = ({ children, ...props }) =>

export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInfosProps) => {
const { translate } = useInternationalization()
const [showMore, setShowMore] = useState(false)
const [shouldSeeMoreButton, setShouldSeeMoreButton] = useState(false)
const infosRef = useRef<HTMLDivElement | null>(null)

const { data: paymentProvidersData } = usePaymentProvidersListForCustomerMainInfosQuery({
variables: { limit: 1000 },
Expand Down Expand Up @@ -272,15 +268,6 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf
(integration) => integration?.id === customer?.salesforceCustomer?.integrationId,
) as SalesforceIntegration

const updateRef = useCallback(
(node: HTMLDivElement) => {
if (customer && node) {
setShouldSeeMoreButton(node.childNodes.length >= SHOW_MORE_THRESHOLD)
}
},
[customer],
)

if (loading || !customer)
return (
<LoadingDetails>
Expand Down Expand Up @@ -327,24 +314,16 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf

return (
<DetailsBlock>
<SectionHeader>
<Typography variant="subhead">{translate('text_6250304370f0f700a8fdc27d')}</Typography>

<Button variant="quaternary" onClick={onEdit}>
{translate('text_626162c62f790600f850b75a')}
</Button>
</SectionHeader>
<InfosBlock
ref={(node) => {
infosRef.current = node

if (node) {
updateRef(node)
}
<PageSectionTitle
title={translate('text_6250304370f0f700a8fdc27d')}
subtitle={translate('text_1737059551511f5acxkfz7p4')}
action={{
title: translate('text_626162c62f790600f850b75a'),
onClick: () => onEdit?.(),
}}
data-id="customer-info-list"
$showMore={showMore}
>
/>

<InfosBlock data-id="customer-info-list">
{customerType && (
<div>
<Typography variant="caption">{translate('text_1726128938631ioz4orixel3')}</Typography>
Expand Down Expand Up @@ -707,25 +686,6 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf
</div>
))}
</InfosBlock>
{shouldSeeMoreButton && !showMore && (
<ShowMoreButton
onClick={() => {
const hiddenItems = Array.from(
infosRef.current?.querySelectorAll(
`*:nth-of-type(n + ${SHOW_MORE_THRESHOLD})`,
) as NodeListOf<HTMLElement>,
)

hiddenItems?.forEach((item) => {
item.style.display = 'block'
})

setShowMore(true)
}}
>
{translate('text_6670a2a7ae3562006c4ee3ce')}
</ShowMoreButton>
)}
</DetailsBlock>
)
}
Expand All @@ -746,15 +706,10 @@ const DetailsBlock = styled.div`
}
`

const InfosBlock = styled.div<{ $showMore: boolean }>`
const InfosBlock = styled.div`
> *:not(:last-child) {
margin-bottom: ${theme.spacing(3)};
}

// Hide all items after the threshold
> *:nth-child(n + ${SHOW_MORE_THRESHOLD}) {
${({ $showMore }) => ($showMore ? 'display: block;' : 'display: none;')}
}
`

const SectionHeader = styled.div`
Expand All @@ -763,8 +718,3 @@ const SectionHeader = styled.div`
justify-content: space-between;
margin-bottom: ${theme.spacing(4)};
`

const ShowMoreButton = styled.span`
color: ${theme.palette.primary[600]};
cursor: pointer;
`
Loading
Loading