Skip to content
Draft
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
128 changes: 128 additions & 0 deletions frontend/web/components/modals/payment/Payment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { FC, useEffect } from 'react'
import Constants from 'common/constants'
import InfoMessage from 'components/InfoMessage'
import BlockedOrgInfo from 'components/BlockedOrgInfo'
import { PricingToggle } from './PricingToggle'
import { PricingPanel } from './PricingPanel'
import { startupFeatures, enterpriseFeatures } from './pricingFeatures'
import {
CONTACT_US_URL,
ON_PREMISE_HOSTING_URL,
SUPPORT_EMAIL,
SUPPORT_EMAIL_URL,
} from './constants'
import { usePaymentState } from './hooks'

type PaymentProps = {
viewOnly?: boolean
isDisableAccountText?: string
}

export const Payment: FC<PaymentProps> = ({
isDisableAccountText,
viewOnly,
}) => {
const { isAWS, plan, setYearly, yearly } = usePaymentState()

useEffect(() => {
API.trackPage(Constants.modals.PAYMENT)
}, [])

if (isAWS) {
return (
<div className='col-md-8'>
<InfoMessage collapseId='aws-marketplace'>
Customers with AWS Marketplace subscriptions will need to{' '}
<a href={CONTACT_US_URL} target='_blank' rel='noreferrer'>
contact us
</a>
</InfoMessage>
</div>
)
}

return (
<div>
<div className='col-md-12'>
<Row space className='mb-4'>
{isDisableAccountText && (
<div className='d-lg-flex flex-lg-row align-items-end justify-content-between w-100 gap-4'>
<div>
<h4>
{isDisableAccountText}{' '}
<a target='_blank' href={SUPPORT_EMAIL_URL} rel='noreferrer'>
{SUPPORT_EMAIL}
</a>
</h4>
</div>
<div>
<BlockedOrgInfo />
</div>
</div>
)}
</Row>

<PricingToggle isYearly={yearly} onChange={setYearly} />

<Row className='pricing-container align-start'>
<PricingPanel
title='Start-Up'
icon='flash'
priceYearly='40'
priceMonthly='45'
isYearly={yearly}
viewOnly={viewOnly}
chargebeePlanId={
yearly
? Project.plans?.startup?.annual
: Project.plans?.startup?.monthly
}
isPurchased={plan.includes('startup')}
isDisableAccount={isDisableAccountText}
features={startupFeatures}
/>

<PricingPanel
title='Enterprise'
icon='flash'
iconFill='white'
isYearly={yearly}
viewOnly={viewOnly}
isEnterprise
features={enterpriseFeatures}
headerContent={
<>
Optional{' '}
<a
className='text-primary fw-bold'
target='_blank'
href={ON_PREMISE_HOSTING_URL}
rel='noreferrer'
>
On Premise
</a>{' '}
or{' '}
<a
className='text-primary fw-bold'
target='_blank'
href={ON_PREMISE_HOSTING_URL}
rel='noreferrer'
>
Private Cloud
</a>{' '}
Install
</>
}
/>
</Row>
<div className='text-center mt-4'>
*Need something in-between our Enterprise plan for users or API
limits?
<div>
<a href={CONTACT_US_URL}>Reach out</a> to us and we'll help you out
</div>
</div>
</div>
</div>
)
}
50 changes: 50 additions & 0 deletions frontend/web/components/modals/payment/PaymentButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { FC, ReactNode } from 'react'
import { usePaymentState } from './hooks'
import { useChargebeeCheckout } from './hooks'

type PaymentButtonProps = {
'data-cb-plan-id'?: string
className?: string
children?: ReactNode
isDisableAccount?: string
}

export const PaymentButton: FC<PaymentButtonProps> = (props) => {
const { hasActiveSubscription, organisation } = usePaymentState()
const { openCheckout } = useChargebeeCheckout({
onSuccess: props.isDisableAccount
? () => {
window.location.href = '/organisations'
}
: undefined,
organisationId: organisation?.id,
})

if (hasActiveSubscription) {
return (
<a
onClick={() => {
const planId = props['data-cb-plan-id']
if (planId) {
openCheckout(planId)
}
}}
className={props.className}
href='#'
>
{props.children}
</a>
)
}

return (
<a
href='javascript:void(0)'
data-cb-type='checkout'
data-cb-plan-id={props['data-cb-plan-id']}
className={props.className}
>
{props.children}
</a>
)
}
28 changes: 28 additions & 0 deletions frontend/web/components/modals/payment/PricingFeaturesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import Icon from 'components/Icon'
import { PRIMARY_ICON_COLOR } from './constants'
import { PricingFeature } from './types'

export type PricingFeaturesListProps = {
features: PricingFeature[]
}

export const PricingFeaturesList = ({ features }: PricingFeaturesListProps) => {
return (
<ul className='pricing-features mb-0 px-2'>
{features.map((feature, index) => (
<li key={index}>
<Row className='mb-3 pricing-features-item'>
<span>
<Icon
name='checkmark-circle'
fill={feature.iconFill || PRIMARY_ICON_COLOR}
/>
</span>
<div className='ml-2'>{feature.text}</div>
</Row>
</li>
))}
</ul>
)
}
162 changes: 162 additions & 0 deletions frontend/web/components/modals/payment/PricingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React, { ReactNode } from 'react'
import classNames from 'classnames'
import Icon, { IconName } from 'components/Icon'
import Button from 'components/base/forms/Button'
import { PricingFeaturesList } from './PricingFeaturesList'
import { PaymentButton } from './PaymentButton'
import { openChat } from 'common/loadChat'
import { PricingFeature } from './types'

export type PricingPanelProps = {
title: string
icon?: string
iconFill?: string
priceMonthly?: string
priceYearly?: string
isYearly: boolean
viewOnly?: boolean
chargebeePlanId?: string
isPurchased?: boolean
isEnterprise?: boolean
isDisableAccount?: string
features: PricingFeature[]
headerContent?: ReactNode
onContactSales?: () => void
}

export const PricingPanel = ({
chargebeePlanId,
features,
headerContent,
icon = 'flash',
iconFill,
isDisableAccount,
isEnterprise,
isPurchased,
isYearly,
onContactSales,
priceMonthly,
priceYearly,
title,
viewOnly,
}: PricingPanelProps) => {
return (
<Flex
className={classNames('pricing-panel p-2', {
'bg-primary900 text-white': isEnterprise,
})}
>
<div className='panel panel-default'>
<div
className='panel-content p-3 pt-4'
style={{
backgroundColor: 'rgba(39, 171, 149, 0.08)',
minHeight: '320px',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: '200px',
}}
>
<div style={{ flex: '0 0 auto' }}>
{headerContent && (
<span
className={classNames('featured', {
'text-body': !isEnterprise,
'text-white': isEnterprise,
})}
>
{headerContent}
</span>
)}
<Row className='pt-4 justify-content-center'>
<Icon
name={icon as IconName}
width={32}
fill={iconFill || undefined}
/>
<h4
className={classNames('mb-0 ml-2', {
'text-white': isEnterprise,
})}
>
{title}
</h4>
</Row>

{priceYearly && priceMonthly && (
<Row className='pt-3 justify-content-center'>
<h5 className='mb-0 align-self-start'>$</h5>
<h1 className='mb-0 d-flex align-items-end'>
{isYearly ? priceYearly : priceMonthly}{' '}
<h5 className='fs-lg mb-0'>/mo</h5>
</h1>
</Row>
)}

{isEnterprise && (
<Row className='pt-3 justify-content-center'>
<div className='pricing-type text-secondary'>
Maximum security and control
</div>
</Row>
)}
</div>

<div style={{ flex: '1 1 auto' }} />

<div style={{ flex: '0 0 auto' }}>
{!viewOnly && !isEnterprise && chargebeePlanId && (
<>
<PaymentButton
data-cb-plan-id={chargebeePlanId}
className={classNames(
'btn btn-primary btn-lg full-width mt-3',
)}
isDisableAccount={isDisableAccount}
>
{isPurchased ? 'Purchased' : '14 Day Free Trial'}
</PaymentButton>
</>
)}

{!viewOnly && isEnterprise && (
<Button
onClick={() => {
if (onContactSales) {
onContactSales()
} else {
openChat()
}
}}
className='full-width btn-lg btn-tertiary mt-3'
>
Contact Sales
</Button>
)}
</div>
</div>
</div>

<div className='panel-footer mt-3'>
<h5
className={classNames('m-2 mb-4', {
'text-white': isEnterprise,
})}
>
All from{' '}
<span className={isEnterprise ? 'text-secondary' : 'text-primary'}>
{isEnterprise ? 'Start-Up,' : 'Free,'}
</span>{' '}
plus
</h5>
<PricingFeaturesList features={features} />
</div>
</div>
</Flex>
)
}
Loading
Loading