Skip to content

Commit

Permalink
[Landing] Support "custom" pricing plan
Browse files Browse the repository at this point in the history
  • Loading branch information
brunolemos committed Feb 2, 2020
1 parent b0c25cb commit ad58d3b
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 52 deletions.
22 changes: 13 additions & 9 deletions landing/src/components/common/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { ReactElement } from 'react'

export interface TabsProps<TabId extends string> {
className?: string
children: Array<ReactElement<TabProps<TabId>>>
children: Array<ReactElement<TabProps<TabId>> | false | null>
onTabChange: (id: TabId) => void
}

Expand All @@ -18,14 +18,18 @@ export function Tabs<TabId extends string>(props: TabsProps<TabId>) {
className,
)}
>
{React.Children.map(children, child => (
<div
className="cursor-pointer"
onClick={() => onTabChange(child.props.id)}
>
{child}
</div>
))}
{React.Children.map(
children,
child =>
!!child && (
<div
className="cursor-pointer"
onClick={() => onTabChange(child.props.id)}
>
{child}
</div>
),
)}
</div>
</div>
)
Expand Down
60 changes: 39 additions & 21 deletions landing/src/components/sections/pricing/PricingPlanBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
: _priceLabel

const subtitle = `${
forceShowAsMonthly
localizedPlan.type === 'custom' && !Number(priceLabelWithoutCents)
? ' '
: forceShowAsMonthly
? `${plan.type === 'team' ? '/user' : ''}/month`
: localizedPlan.interval
? formatInterval(localizedPlan)
Expand Down Expand Up @@ -125,11 +127,6 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
`*Billed ${
localizedPlan.amount % 100 > 50 ? '~' : ''
}${_roundedPriceLabelWithInterval}`
} else if (
forceShowAsMonthly &&
plans.find(p => p && p.interval !== 'month')
) {
footerText = `${footerText || ' '}\n`
}

if (!localizedPlan.interval && localizedPlan.amount) {
Expand All @@ -138,6 +135,17 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
'One-time payment (no subscription)'
}

if (
!footerText &&
localizedPlan.type === 'custom' &&
buttonLink &&
buttonLink.startsWith('mailto:')
) {
footerText =
(footerText ? `${footerText}\n` : footerText) +
`Contact us: ${buttonLink.replace('mailto:', '')}`
}

if (!footerText && localizedPlan.interval && localizedPlan.amount) {
footerText =
(footerText ? `${footerText}\n` : footerText) + localizedPlan.interval ===
Expand All @@ -146,6 +154,15 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
: 'Cancel anytime with one click'
}

if (
!footerText &&
forceShowAsMonthly &&
plans.find(p => p && p.interval !== 'month') &&
modifiedAmount === localizedPlan.amount
) {
footerText = `${footerText || ' '}\n`
}

return (
<section
className={classNames(
Expand Down Expand Up @@ -191,17 +208,26 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
</div>

<div className="text-5xl leading-snug font-bold text-default">
{`${priceLabelWithoutCents}`}
{!!priceLabelCents && (
<small>
<small>
{localizedPlan.type === 'custom' &&
!Number(priceLabelWithoutCents) ? (
'$?'
) : (
<>
{priceLabelWithoutCents}
{!!priceLabelCents && (
<small>
<small>
<small className="text-muted-65">{priceLabelCents}</small>
<small>
<small>
<small className="text-muted-65">
{priceLabelCents}
</small>
</small>
</small>
</small>
</small>
</small>
</small>
)}
</>
)}
</div>

Expand All @@ -213,14 +239,6 @@ export function PricingPlanBlock(props: PricingPlanBlockProps) {
<div className="mb-2 text-sm text-muted-65">
&nbsp;{subtitle}&nbsp;
</div>
{/* {localizedPlan.interval ? (
<div className="text-sm text-muted-65">{`/${
localizedPlan.intervalCount > 1 ? `${localizedPlan.intervalCount}-` : ''
}${localizedPlan.interval}`}</div>
) : (
<div className="text-sm text-muted-65">&nbsp;</div>
)} */}

<div className="pb-6" />
</>
)}
Expand Down
68 changes: 47 additions & 21 deletions landing/src/components/sections/pricing/PricingPlans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,25 @@ export function PricingPlans(_props: PricingPlansProps) {
const { authData } = useAuth()
const { dealCode, plans } = usePlans()

const shouldShowPlanTypeTabs =
plans.some(plan => plan && plan.type !== 'team') &&
plans.some(plan => plan && plan.type === 'team')
const planTypesCounters = plans.reduce<
Partial<Record<NonNullable<PlanType>, number>>
>((obj, plan) => {
if (!(plan && plan.type)) return obj
obj[plan.type] = (obj[plan.type] || 0) + 1
return obj
}, {})

const hasMultiplePlanTypes = Object.keys(planTypesCounters).length > 1

const [_tab, setTab] = useState<PlanType | undefined>(undefined)
const tab =
_tab ||
(shouldShowPlanTypeTabs &&
(hasMultiplePlanTypes &&
(authData && authData.plan && authData.plan.type) === 'team'
? 'team'
: shouldShowPlanTypeTabs
: (authData && authData.plan && authData.plan.type) === 'custom'
? 'custom'
: hasMultiplePlanTypes
? 'individual'
: undefined)

Expand All @@ -38,7 +46,7 @@ export function PricingPlans(_props: PricingPlansProps) {
plan =>
!!(
plan &&
((!tab && !shouldShowPlanTypeTabs) ||
((!tab && !hasMultiplePlanTypes) ||
(plan.type === tab || (tab === 'individual' && !plan.type)))
),
)
Expand All @@ -57,31 +65,49 @@ export function PricingPlans(_props: PricingPlansProps) {
totalNumberOfVisiblePlans={filteredPlans.length}
/>
) : plan ? (
<PricingPlanBlock
key={`pricing-plan-${plan.cannonicalId}`}
banner
buttonLink={`/download?plan=${plan.cannonicalId}`}
buttonLabel="Download"
plan={plan}
totalNumberOfVisiblePlans={filteredPlans.length}
/>
plan.type === 'custom' ? (
<PricingPlanBlock
key={`pricing-plan-${plan.cannonicalId}`}
banner
buttonLink={`mailto:[email protected]`}
buttonLabel="Contact us"
plan={plan}
totalNumberOfVisiblePlans={filteredPlans.length}
/>
) : (
<PricingPlanBlock
key={`pricing-plan-${plan.cannonicalId}`}
banner
buttonLink={`/download?plan=${plan.cannonicalId}`}
buttonLabel="Download"
plan={plan}
totalNumberOfVisiblePlans={filteredPlans.length}
/>
)
) : null,
)
}, [plans, tab])

return (
<div className="container">
{!!shouldShowPlanTypeTabs && (
{!!hasMultiplePlanTypes && (
<Tabs<NonNullable<PlanType>>
className="mb-6"
onTabChange={id => setTab(id)}
>
<Tabs.Tab
active={tab === 'individual'}
id="individual"
title="Individual"
/>
<Tabs.Tab active={tab === 'team'} id="team" title="Team" />
{!!planTypesCounters.individual && (
<Tabs.Tab
active={tab === 'individual'}
id="individual"
title="Individual"
/>
)}
{!!planTypesCounters.team && (
<Tabs.Tab active={tab === 'team'} id="team" title="Team" />
)}
{!!planTypesCounters.custom && (
<Tabs.Tab active={tab === 'custom'} id="custom" title="Custom" />
)}
</Tabs>
)}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/types/devhub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ export type PlatformCategory = DownloadOption['category']
export type Platform = DownloadOption['platform']

export type PlanSource = 'stripe' | 'paddle' | 'none' // | 'github_marketplace' | 'opencollective' | 'appstore' | 'playstore'
export type PlanType = 'individual' | 'team' | undefined
export type PlanType = 'individual' | 'team' | 'custom' | undefined

export interface Plan {
id: PlanID
Expand Down

0 comments on commit ad58d3b

Please sign in to comment.