Skip to content
Open
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
41 changes: 41 additions & 0 deletions frontend/common/hooks/useScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect, useState } from 'react'

type ScriptState = {
ready: boolean
error: boolean
}

export const useScript = (url: string): ScriptState => {
const [state, setState] = useState<ScriptState>({
error: false,
ready: false,
})

useEffect(() => {
const existing = document.querySelector(`script[src="${url}"]`)
if (existing) {
setState({ error: false, ready: true })
return
}

const script = document.createElement('script')
script.src = url
script.async = true

script.addEventListener('load', () => {
setState({ error: false, ready: true })
})

script.addEventListener('error', () => {
setState({ error: true, ready: false })
})

document.head.appendChild(script)

return () => {
document.head.removeChild(script)
}
}, [url])

return state
}
4 changes: 4 additions & 0 deletions frontend/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ declare global {
}
const PanelSearch: typeof Component
const CodeHelp: typeof Component
// Chargebee SDK (loaded via useScript)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Chargebee: any

interface Window {
E2E: boolean
$crisp: Crisp
Expand Down
149 changes: 149 additions & 0 deletions frontend/web/components/modals/payment/Payment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { FC, useEffect } from 'react'
import Constants from 'common/constants'
import InfoMessage from 'components/InfoMessage'
import BlockedOrgInfo from 'components/BlockedOrgInfo'
import { Organisation } from 'common/types/responses'
import { PricingToggle } from './PricingToggle'
import { PricingPanel } from './PricingPanel'
import { startupFeatures, enterpriseFeatures } from './pricingFeatures'
import {
CHARGEBEE_SCRIPT_URL,
CONTACT_US_URL,
ON_PREMISE_HOSTING_URL,
SUPPORT_EMAIL,
SUPPORT_EMAIL_URL,
} from './constants'
import { useScript } from 'common/hooks/useScript'
import { usePaymentState } from './hooks'
import { initChargebee } from './chargebee'

export type PaymentProps = {
isDisableAccountText?: string
organisation: Organisation
isPaymentsEnabled?: boolean
}

export const Payment: FC<PaymentProps> = ({
isDisableAccountText,
isPaymentsEnabled = false,
organisation,
}) => {
const { error, ready } = useScript(CHARGEBEE_SCRIPT_URL)
const { hasActiveSubscription, isAWS, plan, setYearly, yearly } =
usePaymentState({ organisation })

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

useEffect(() => {
if (ready && !error) {
initChargebee({ isPaymentsEnabled })
}
}, [ready, error, isPaymentsEnabled])

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>
)
}

if (!ready) {
return (
<div className='text-center'>
<Loader />
</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'
priceYearly='40'
priceMonthly='45'
isYearly={yearly}
chargebeePlanId={
yearly
? Project.plans?.startup?.annual
: Project.plans?.startup?.monthly
}
isPurchased={plan.includes('startup')}
isDisableAccount={isDisableAccountText}
features={startupFeatures}
hasActiveSubscription={hasActiveSubscription}
organisationId={organisation.id}
/>

<PricingPanel
title='Enterprise'
isYearly={yearly}
isEnterprise
features={enterpriseFeatures}
hasActiveSubscription={hasActiveSubscription}
organisationId={organisation.id}
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>
)
}
54 changes: 54 additions & 0 deletions frontend/web/components/modals/payment/PaymentButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { FC, ReactNode } from 'react'
import { useChargebeeCheckout } from './hooks'

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

export const PaymentButton: FC<PaymentButtonProps> = ({
children,
className,
hasActiveSubscription,
isDisableAccount,
organisationId,
...rest
}) => {
const planId = rest['data-cb-plan-id']
const { isLoading, openCheckout } = useChargebeeCheckout({
onSuccess: isDisableAccount
? () => {
window.location.href = '/organisations'
}
: undefined,
organisationId,
})

if (hasActiveSubscription) {
return (
<button
onClick={() => planId && openCheckout(planId)}
className={className}
type='button'
disabled={isLoading}
>
{isLoading ? 'Processing...' : children}
</button>
)
}

return (
<button
type='button'
data-cb-type='checkout'
data-cb-plan-id={planId}
className={className}
>
{children}
</button>
)
}
24 changes: 24 additions & 0 deletions frontend/web/components/modals/payment/PricingFeaturesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import Icon from 'components/Icon'
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 className={feature.iconClass || 'text-success'}>
<Icon name='checkmark-circle' />
</span>
<div className='ml-2'>{feature.text}</div>
</Row>
</li>
))}
</ul>
)
}
Loading
Loading