Skip to content

Commit

Permalink
feat(plan) add new graduated percentage charge model
Browse files Browse the repository at this point in the history
  • Loading branch information
ansmonjol committed Aug 17, 2023
1 parent ad56dc9 commit f315030
Show file tree
Hide file tree
Showing 21 changed files with 1,027 additions and 33 deletions.
10 changes: 10 additions & 0 deletions ditto/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,16 @@
"text_6435888d7cc86500646d8977": "Usage-based charges",
"text_6435895831d323008a47911f": "Metric already used in the plan, customers will be charged several times.",
"text_64358e074a3b7500714f256c": "If true, all charges will be aggregated and invoiced monthly, even if the plan is yearly.",
"text_64de472463e2da6b31737db0": "Graduated percentage pricing",
"text_64de472463e2da6b31737db8": "For instance, charge 5% + $1.00 per transaction for the first 1000 units and then $3% + 0.50 per transaction for the next ones.",
"text_64de472463e2da6b31737de0": "Rate",
"text_64de472463e2da6b31737de8": "Fixed fee",
"text_64de472463e2da6b31737df2": "Flat fee",
"text_64de472563e2da6b31737e6f": "First {{units}} units, {{rate}} of units + {{fixedAmount}} / transaction + {{flatAmount}} flat fee",
"text_64de472563e2da6b31737e75": "After {{units}} units, {{rate}} of units + {{fixedAmount}} / transaction + {{flatAmount}} flat fee",
"text_64de5dd470cdf80100c15fdb": "For all units, {{rate}} of units + {{fixedAmount}} / transaction + {{flatAmount}} flat fee",
"text_64de472563e2da6b31737e73": "Fee applied to each event received",
"text_64de472563e2da6b31737e77": "Fee applied to the whole tier",
"text_643e592657fc1ba5ce110b9e": "Add a spending minimum",
"text_64463aaa34904c00a23be4f7": "True-up",
"text_643e592657fc1ba5ce110c30": "Spending minimum",
Expand Down
4 changes: 4 additions & 0 deletions ditto/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@ sources:
fileName: 👍 [Ready for dev] - Settings - Net payment term
- name: 👍 [Ready for dev] - Add-on - Add tax to add-ons & one off invoice
id: 64d40b7c611d58bb88ec7d63
fileName: 👍 [Ready for dev] - Add-on - Add tax to add-ons & one off invoice
- name: 👍 [Ready for dev] - Plan - Quarterly plan interval
id: 64d63579f0cd15ff208e69e3
fileName: 👍 [Ready for dev] - Plan - Quarterly plan interval
- name: 👍 [Ready for dev] - Plans - Graduated percentage charge model
id: 64de471ef3038f0ad36833f8
format: flat
variants: true
4 changes: 2 additions & 2 deletions ditto/de.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"__variant-name": "German",
"__variant-description": "",
"text_641d6ae1d947c400671e6abb": "Etwas ist schiefgelaufen",
"text_641d6aee014c8d00c1425cdd": "Bitte aktualisieren Sie die Seite oder kontaktieren Sie uns, wenn der Fehler weiterhin besteht.",
"text_641d6b00ef96c1008754734d": "Seite aktualisieren",
Expand Down Expand Up @@ -31,6 +29,8 @@
"text_6419c64eace749372fc72b54": "Bezahlt",
"text_6419c64eace749372fc72b62": "PDF herunterladen",
"text_641c6acee4bc20004e62c534": "Es ist ein Problem aufgetreten, die Url ist ungültig oder abgelaufen.",
"__variant-name": "German",
"__variant-description": "",
"text_64188b3d9735d5007d712249": "730,00 $",
"text_64188b3d9735d5007d71225c": "Deine Rechnung von {{organization}} #LAG-1234-567-981",
"text_64188b3d9735d5007d712260": "<[email protected]>",
Expand Down
4 changes: 2 additions & 2 deletions ditto/fr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"__variant-name": "French",
"__variant-description": "",
"text_641d6ae1d947c400671e6abb": "Quelque chose n'a pas fonctionné",
"text_641d6aee014c8d00c1425cdd": "Veuillez actualiser la page ou nous contacter si l'erreur persiste.",
"text_641d6b00ef96c1008754734d": "Actualiser la page",
Expand Down Expand Up @@ -31,6 +29,8 @@
"text_6419c64eace749372fc72b54": "Payé",
"text_6419c64eace749372fc72b62": "Télécharger le PDF",
"text_641c6acee4bc20004e62c534": "Un problème s'est produit, l’url n'est pas valide ou a expiré.",
"__variant-name": "French",
"__variant-description": "",
"text_64188b3d9735d5007d712249": "730.00 $",
"text_64188b3d9735d5007d71225c": "Votre facture nº LAG-1234-567-981 de {{organization}}",
"text_64188b3d9735d5007d712260": "<[email protected]>",
Expand Down
8 changes: 4 additions & 4 deletions ditto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ module.exports = {
"project_642d5eac2dc55f7f81e01dd4": {
"base": require('./-ready-for-dev---plans---create-a-plan-ui-refacto__base.json')
},
"project_64de471ef3038f0ad36833f8": {
"base": require('./-ready-for-dev---plans---graduated-percentage-charge-model__base.json')
},
"project_643e59213ea23c04674eba8c": {
"base": require('./-ready-for-dev---plans---set-a-minimum-spending-on-charges__base.json')
},
Expand Down Expand Up @@ -198,10 +201,7 @@ module.exports = {
"base": require('./customers---edit--delete-a-customer__base.json')
},
"ditto_component_library": {
"base": require('./ditto-component-library__base.json'),
"de": require('./ditto-component-library__de.json'),
"fr": require('./ditto-component-library__fr.json'),
"nb": require('./ditto-component-library__nb.json')
"base": require('./ditto-component-library__base.json')
},
"project_623b3ac9459a5d00df324533": {
"base": require('./documentation-asset__base.json')
Expand Down
4 changes: 2 additions & 2 deletions ditto/nb.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"__variant-name": "Norwegian",
"__variant-description": "",
"text_641d6ae1d947c400671e6abb": "Noe gikk galt",
"text_641d6aee014c8d00c1425cdd": "Oppdater siden eller kontakt oss hvis feilen vedvarer.",
"text_641d6b00ef96c1008754734d": "Oppdater siden",
Expand Down Expand Up @@ -31,6 +29,8 @@
"text_6419c64eace749372fc72b54": "Betalt",
"text_6419c64eace749372fc72b62": "Last ned PDF",
"text_641c6acee4bc20004e62c534": "Noe gikk galt, token er ugyldig eller utløpt.",
"__variant-name": "Norwegian",
"__variant-description": "",
"text_64188b3d9735d5007d712249": "$730,00",
"text_64188b3d9735d5007d71225c": "Faktura fra {{organization}} #LAG-1234-567-981",
"text_64188b3d9735d5007d712262": "Til kundene dine",
Expand Down
9 changes: 9 additions & 0 deletions src/components/designSystem/BetaChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Chip, ChipSize } from './Chip'

type BetaChipProps = {
size?: ChipSize
}

export const BetaChip = ({ size = 'small' }: BetaChipProps) => {
return <Chip type="beta" label="Beta" size={size} />
}
27 changes: 23 additions & 4 deletions src/components/designSystem/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ enum ChipVariantEnum {
enum ChipTypeEnum {
default = 'default',
error = 'error',
beta = 'beta',
}

type ChipSize = 'small' | 'medium'
export type ChipSize = 'xsmall' | 'small' | 'medium'
type ChipVariant = keyof typeof ChipVariantEnum

interface ChipGenericProps {
Expand Down Expand Up @@ -88,8 +89,20 @@ export const Chip = ({
)}
{avatarProps && <Avatar size="small" variant="user" {...avatarProps} />}
<Typography
variant={variant === ChipVariantEnum.secondary ? 'body' : 'captionHl'}
color={type === ChipTypeEnum.error ? 'danger600' : 'textSecondary'}
variant={
type === ChipTypeEnum.beta
? 'captionCode'
: variant === ChipVariantEnum.secondary
? 'body'
: 'captionHl'
}
color={
type === ChipTypeEnum.error
? 'danger600'
: type === ChipTypeEnum.beta
? 'info600'
: 'textSecondary'
}
>
{label}
</Typography>
Expand Down Expand Up @@ -120,7 +133,8 @@ export const Chip = ({
const Container = styled.div`
min-height: 32px;
height: fit-content;
border: 1px solid ${theme.palette.grey[300]};
outline: 1px solid ${theme.palette.grey[300]};
outline-offset: -1px;
background-color: ${theme.palette.grey[100]};
padding: ${theme.spacing(1)} ${theme.spacing(2)};
box-sizing: border-box;
Expand All @@ -138,6 +152,11 @@ const Container = styled.div`
}
/* Size */
&.chip-container--xsmall {
min-height: 0;
padding: 0 ${theme.spacing(1)};
border-radius: 4px;
}
&.chip-container--medium {
padding: 10px ${theme.spacing(3)};
}
Expand Down
1 change: 1 addition & 0 deletions src/components/designSystem/Typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum ColorTypeEnum {
grey600 = 'grey.600',
grey500 = 'grey.500',
grey400 = 'grey.400',
info600 = 'info.600',
primary600 = 'primary.600',
danger600 = 'error.600',
success600 = 'success.600',
Expand Down
9 changes: 7 additions & 2 deletions src/components/form/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const Radio = forwardRef<HTMLDivElement, RadioProps>(
<RadioIcon className="radio-icon" />
)}
</RadioContainer>
<div>
<RadioLabelWrapper>
<Typography
color={disabled ? 'disabled' : 'textSecondary'}
component={(labelProps) => <label htmlFor={name} {...labelProps} />}
Expand All @@ -66,7 +66,7 @@ export const Radio = forwardRef<HTMLDivElement, RadioProps>(
) : (
sublabel
))}
</div>
</RadioLabelWrapper>
</Container>
)
}
Expand All @@ -75,6 +75,7 @@ export const Radio = forwardRef<HTMLDivElement, RadioProps>(
Radio.displayName = 'Radio'

const Container = styled.div`
width: 100%;
display: flex;
align-items: center;
cursor: pointer;
Expand Down Expand Up @@ -138,3 +139,7 @@ const RadioContainer = styled.div`
padding: 0;
}
`

const RadioLabelWrapper = styled.div`
width: 100%;
`
80 changes: 73 additions & 7 deletions src/components/plans/ChargeAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
AggregationTypeEnum,
useGetTaxesForChargesLazyQuery,
TaxForPlanChargeAccordionFragment,
GraduatedPercentageChargeFragmentDoc,
} from '~/generated/graphql'
import { AmountInput, ButtonSelector, ComboBox, Switch } from '~/components/form'
import { GraduatedChargeTable } from '~/components/plans/GraduatedChargeTable'
Expand All @@ -44,9 +45,11 @@ import { VolumeChargeTable } from './VolumeChargeTable'
import { ConditionalChargeWrapper } from './ConditionalChargeWrapper'
import { RemoveChargeWarningDialogRef } from './RemoveChargeWarningDialog'
import { ChargeOptionsAccordion } from './ChargeOptionsAccordion'
import { GraduatedPercentageChargeTable } from './GraduatedPercentageChargeTable'

import { PremiumWarningDialogRef } from '../PremiumWarningDialog'
import { Item } from '../form/ComboBox/ComboBoxItem'
import { BetaChip } from '../designSystem/BetaChip'

interface ChargeAccordionProps {
id: string
Expand Down Expand Up @@ -100,6 +103,7 @@ gql`
...TaxForPlanChargeAccordion
}
...GraduatedCharge
...GraduatedPercentageCharge
...VolumeRanges
...PackageCharge
...PercentageCharge
Expand All @@ -120,6 +124,7 @@ gql`
}
${GraduatedChargeFragmentDoc}
${GraduatedPercentageChargeFragmentDoc}
${VolumeRangesFragmentDoc}
${PackageChargeFragmentDoc}
${PercentageChargeFragmentDoc}
Expand Down Expand Up @@ -179,6 +184,14 @@ export const ChargeAccordion = memo(
const handleUpdate = useCallback(
(name: string, value: string | boolean | TaxForPlanChargeAccordionFragment[]) => {
if (name === 'chargeModel') {
// IMPORTANT: This check should stay first in this function
// If user is not premium and try to switch to graduated percentage pricing
// We should show the premium modal and prevent any formik value change
if (!isPremium && value === ChargeModelEnum.GraduatedPercentage) {
premiumWarningDialogRef.current?.openDialog()
return
}

// Reset pay in advance when switching charge model
if (
value === ChargeModelEnum.Volume ||
Expand All @@ -191,6 +204,7 @@ export const ChargeAccordion = memo(
// Reset prorated when switching charge model
if (
(localCharge.billableMetric.recurring && value === ChargeModelEnum.Graduated) ||
value === ChargeModelEnum.GraduatedPercentage ||
value === ChargeModelEnum.Package ||
value === ChargeModelEnum.Percentage
) {
Expand All @@ -213,8 +227,10 @@ export const ChargeAccordion = memo(
[
formikProps,
index,
isPremium,
localCharge.billableMetric.aggregationType,
localCharge.billableMetric.recurring,
premiumWarningDialogRef,
]
)

Expand Down Expand Up @@ -283,6 +299,7 @@ export const ChargeAccordion = memo(
return (
(localCharge.billableMetric.recurring &&
localCharge.chargeModel === ChargeModelEnum.Graduated) ||
localCharge.chargeModel === ChargeModelEnum.GraduatedPercentage ||
localCharge.chargeModel === ChargeModelEnum.Package ||
localCharge.chargeModel === ChargeModelEnum.Percentage
)
Expand Down Expand Up @@ -379,30 +396,55 @@ export const ChargeAccordion = memo(
<Alert type="warning">{translate('text_6435895831d323008a47911f')}</Alert>
)}
<ComboBox
sortValues={false}
name="chargeModel"
disabled={disabled}
label={translate('text_624c5eadff7db800acc4ca0d')}
label={
<InlineComboboxLabel>
<Typography variant="captionHl" color="textSecondary">
{translate('text_624c5eadff7db800acc4ca0d')}
</Typography>
{localCharge.chargeModel === ChargeModelEnum.GraduatedPercentage && (
<BetaChip size="xsmall" />
)}
</InlineComboboxLabel>
}
data={[
{
label: translate('text_624aa732d6af4e0103d40e6f'),
value: ChargeModelEnum.Standard,
},
{
label: translate('text_62793bbb599f1c01522e919f'),
value: ChargeModelEnum.Graduated,
},
...(!localCharge.billableMetric.recurring
? [
{
label: translate('text_62a0b7107afa2700a65ef6e2'),
value: ChargeModelEnum.Percentage,
labelNode: (
<InlineComboboxLabelForPremiumWrapper>
<InlineComboboxLabel>
<Typography variant="body" color="grey700">
{translate('text_64de472463e2da6b31737db0')}
</Typography>
<BetaChip size="xsmall" />
</InlineComboboxLabel>
{!isPremium && <Icon name="sparkles" />}
</InlineComboboxLabelForPremiumWrapper>
),
label: translate('text_64de472463e2da6b31737db0'),
value: ChargeModelEnum.GraduatedPercentage,
},
{
label: translate('text_6282085b4f283b0102655868'),
value: ChargeModelEnum.Package,
},
{
label: translate('text_62a0b7107afa2700a65ef6e2'),
value: ChargeModelEnum.Percentage,
},
]
: []),
{
label: translate('text_624aa732d6af4e0103d40e6f'),
value: ChargeModelEnum.Standard,
},
{
label: translate('text_6304e74aab6dbc18d615f386'),
value: ChargeModelEnum.Volume,
Expand All @@ -415,6 +457,8 @@ export const ChargeAccordion = memo(
? 'text_62ff5d01a306e274d4ffcc06'
: localCharge.chargeModel === ChargeModelEnum.Graduated
? 'text_62793bbb599f1c01522e91a1'
: localCharge.chargeModel === ChargeModelEnum.GraduatedPercentage
? 'text_64de472463e2da6b31737db8'
: localCharge.chargeModel === ChargeModelEnum.Package
? 'text_6282085b4f283b010265586c'
: localCharge.chargeModel === ChargeModelEnum.Volume
Expand Down Expand Up @@ -469,6 +513,16 @@ export const ChargeAccordion = memo(
valuePointer={valuePointer}
/>
)}
{localCharge.chargeModel === ChargeModelEnum.GraduatedPercentage && (
<GraduatedPercentageChargeTable
chargeIndex={index}
currency={currency}
disabled={disabled}
formikProps={formikProps}
propertyCursor={propertyCursor}
valuePointer={valuePointer}
/>
)}
{localCharge.chargeModel === ChargeModelEnum.Percentage && (
<ChargePercentage
chargeIndex={index}
Expand Down Expand Up @@ -784,3 +838,15 @@ const InlineTaxesWrapper = styled.div`
gap: ${theme.spacing(3)};
flex-wrap: wrap;
`

const InlineComboboxLabelForPremiumWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`

const InlineComboboxLabel = styled.div`
display: flex;
align-items: center;
gap: ${theme.spacing(2)};
`
Loading

0 comments on commit f315030

Please sign in to comment.