Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
50327ad
Rough redesign layout change
Aug 31, 2022
691056c
Comment
Sep 1, 2022
32a9c3f
Merge branch 'main' into refresh/pricing-pg
Sep 6, 2022
9489ef2
PricingPlan card UI
Sep 6, 2022
773a5ee
Adjust PricingPlanFeature
Sep 6, 2022
6e87204
Enterprise color toggle
Sep 6, 2022
ec47985
Merge branch 'main' into refresh/pricing-pg
Sep 7, 2022
836da9e
Cleanup
Sep 7, 2022
76c3576
Sticky table headers
Sep 7, 2022
4226f35
Lg table overall style setup & sticky headers
Sep 7, 2022
ea7b7ae
Feature list data
Sep 7, 2022
870dbf5
Feature_info dictionary
Sep 8, 2022
90ba6fd
Prettier/Lint
Sep 8, 2022
07508d1
Consolidate pricing data to it's own file
Sep 8, 2022
47c942f
Re org Spotlight & All feature label/tooltip data
Sep 9, 2022
13ae0be
Merge branch 'main' into refresh/pricing-pg
Sep 9, 2022
7a0e603
Resolve merge conflict
Sep 13, 2022
85b07d0
Reuse btns
Sep 13, 2022
751b429
Comment
Sep 13, 2022
4b90b7f
Init accordion
Sep 16, 2022
19ee5a6
Resolve merge conflict
Sep 16, 2022
421001a
Tooltip style adjustments
Sep 17, 2022
57e4376
Type Feature dictionaries
Sep 17, 2022
98d6803
Accordion icon toggle
Sep 18, 2022
1eb0551
Tooltip style
Sep 18, 2022
61220ff
Mars gradient bg
Sep 18, 2022
2553069
FAQ links
Sep 18, 2022
fb51f04
Prettier/Lint
Sep 18, 2022
6ef7dd5
FAQ heading spacing
Sep 18, 2022
fb94f9a
Fix map key error
Sep 18, 2022
08774af
Rm comment
Sep 18, 2022
1f554ea
Table mobile sizing
Sep 19, 2022
36b24b5
Resolve merge conflict
Sep 20, 2022
f32cedc
Lint/Prettier
Sep 20, 2022
740abd1
Rename file
Sep 20, 2022
f53ad5c
Copy change requests
Sep 20, 2022
f226686
Header spacing
Sep 20, 2022
3249602
Meta update
Sep 20, 2022
ea42809
CTA change
Sep 20, 2022
e8c6e5d
Cont copy update
Sep 21, 2022
c2a842e
Rm comment
Sep 21, 2022
8d34745
Cont copy updates
Sep 21, 2022
cd978cb
Mobile feature table
Sep 22, 2022
58747ab
Copy updates & odd icon bug fix
Sep 22, 2022
563e785
Cont table mobile refine
Sep 23, 2022
95e2b76
R merge conflict
Sep 23, 2022
d166519
Copy
Sep 23, 2022
4833f07
Copy
Sep 23, 2022
ff13876
copy edits and prettier
bretthayes Sep 23, 2022
c4f9fd5
Merge branch '4.0-launch' into refresh/pricing-pg
bretthayes Sep 23, 2022
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
237 changes: 44 additions & 193 deletions src/components/Pricing/PricingPlan.tsx
Original file line number Diff line number Diff line change
@@ -1,209 +1,60 @@
import { ReactNode, FunctionComponent } from 'react'

import { buttonStyle, buttonLocation } from '@data'
import CheckIcon from 'mdi-react/CheckIcon'

import { Features, FeatureInfo } from './interfaces'
import { FeatureCluster, SPOTLIGHT_FEATURE_INFO } from './data'
import { PricingPlanFeature } from './PricingPlanFeature'

const FEATURE_INFO: Record<keyof Features, FeatureInfo> = {
codeSearch: {
label: 'Code search',
description:
'Super-fast, intuitive, and powerful code search across 10,000s of repositories, with smart filters and more',
},
codeNavigation: {
label: 'Code navigation',
description: 'Code navigation for 30+ languages, with hovers, definitions, and references across repositories',
},
batchChanges: {
label: 'Batch Changes (available add-on)',
description: 'Apply and track large-scale code changes across all of your repositories and code hosts.',
},
batchChangesTrial: {
label: 'Batch Changes (limited trial)',
description:
'Apply and track large-scale code changes across all of your repositories and code hosts (limited to 5 changesets per batch change).',
},
codeHostIntegration: {
label: '1 code host integration',
description:
'Works with GitHub, GitLab, Bitbucket Server/Cloud, and other popular code hosts (or manually add repositories from any VCS)',
},
api: { label: 'Comprehensive API', description: 'A secure, robust GraphQL API for your repository and code data' },

selfHosted: {
label: 'Self-hosted deployment',
description: 'Deploy with Docker, Docker Compose, or Kubernetes on your own infrastructure',
},
singleSignOn: {
label: 'SSO/SAML',
description: 'Single sign-on user authentication with SAML, OAuth, OpenID Connect, and HTTP auth proxy',
},
userAndAdminRoles: {
label: 'User and admin roles',
description:
'Allow only certain users (site admins) to view and edit site configuration and repository/code host credentials',
},
multipleCodeHosts: {
label: 'Multiple code hosts',
description:
'Sync, search, and browse code from more than one code host on your Sourcegraph instance (such as GitHub.com and GitLab)',
},
repositoryPermissions: {
label: 'Repository permissions',
description:
'Apply the repository permissions from your code host to restrict which repositories a user can search and browse',
},
optimizedRepositoryUpdates: {
label: 'Faster repository updates',
description: "Optimized repository syncing, integrated with your code host's webhooks or event system",
},
privateExtensions: {
label: 'Private extension registry',
description:
'Publish and use internal Sourcegraph extensions (instead of just using the Sourcegraph.com extension registry)',
},
deploymentMetricsAndMonitoring: {
label: 'Deployment monitoring',
description:
'Extensive metrics and dashboards to monitor the performance and health of your Sourcegraph cluster',
},
backupRestore: {
label: 'Backup and restore',
description:
'Officially supported scripts to back up and restore your Sourcegraph instance and all configuration and data',
},
customBranding: {
label: 'Custom branding',
description: 'Show your logo, icon, and other branding in the Sourcegraph UI',
},
onlineTraining: {
label: 'Live training sessions',
description: 'Personalized online training sessions for your organization with our Customer Engineering team',
},
customContractLegalBillingTerms: {
label: 'Custom contracts/billing',
description: "Need us to use your organization's legal contracts or purchasing system?",
},
unlimitedCode: {
label: 'Unlimited code scale',
description:
'Free and Team tiers limit the total amount of searchable code. Enterprise offers options that scale to any size codebase.',
},
managedInstance: {
label: 'Managed instance (available add-on)',
description:
'Managed instances are provisioned and managed by the Sourcegraph team so you can deploy Sourcegraph without having to worry about managing it.',
},
codeInsights: {
label: 'Code Insights (available add-on)',
description: 'Track and visualize trends in your entire codebase — kept automatically up to date.',
},
codeInsightsTrial: {
label: 'Code Insights (limited trial)',
description: `Track and visualize trends in your entire codebase — with visualizations that are kept automatically up to date
(limited to maximum of two global insights without a license).`,
},
}

const FEATURE_ORDER: (keyof Features)[] = [
'codeSearch',
'codeNavigation',
'codeHostIntegration',
'selfHosted',
'multipleCodeHosts',
'unlimitedCode',
'repositoryPermissions',
'userAndAdminRoles',
'batchChanges',
'batchChangesTrial',
'codeInsights',
'codeInsightsTrial',
'api',
'singleSignOn',
'optimizedRepositoryUpdates',
'deploymentMetricsAndMonitoring',
'privateExtensions',
'backupRestore',
'onlineTraining',
'managedInstance',
'customBranding',
'customContractLegalBillingTerms',
]

interface Props {
className?: string

name: string
planProperties: ReactNode
price: ReactNode
features: Features

isFree: boolean

buttonLabel: string
buttonClassName: string
buttonOnClick?: () => void
buttonHref: string
description: string
price: string
features: FeatureCluster[]
buttons: ReactNode
isEnterprise: boolean
}

/**
* A pricing plan on the pricing page.
*/
/** A pricing plan on the pricing page. */
export const PricingPlan: FunctionComponent<Props> = ({
className = '',

name,
price,
planProperties,
description,
features,

isFree,

buttonLabel,
buttonClassName,
buttonOnClick,
buttonHref,
}) => {
const button = (
<a
className={`btn ${buttonClassName} w-50 min-w-250`}
href={buttonHref}
onClick={buttonOnClick}
title={buttonLabel}
data-button-style={buttonStyle.outline}
data-button-location={buttonLocation.body}
data-button-type="cta"
>
{buttonLabel}
</a>
)

return (
<div className={`h-100 card ${className}`}>
<h2 className="mt-3 mb-1 tw-text-center tw-font-semibold">{name}</h2>
<div className="py-3 tw-text-center tw-flex tw-flex-col tw-items-center">
{button}
<div className="mt-4 mb-2 tw-pb-xxs tw-text-xl text-muted">{price}</div>
{planProperties}
</div>
<ol className="px-6 py-3 ml-0 list-group list-group-flush">
{!isFree ? (
<li className="bg-transparent border-0 tw-px-0 tw-text-xl list-group-item">
Everything in the Free tier, plus:
</li>
) : null}
{FEATURE_ORDER.map(feature => (
<div key={FEATURE_INFO[feature].label}>
<PricingPlanFeature
info={FEATURE_INFO[feature]}
value={features[feature]}
tag="li"
className="bg-transparent border-0 tw-px-0 tw-text-xl list-group-item"
buttons,
isEnterprise,
}) => (
<div
className={`h-100 tw-p-md tw-shadow-lg tw-border-t-16 tw-rounded tw-border-gray-200 ${
isEnterprise ? 'tw-border-t-violet-400' : 'tw-border-t-vermillion-300'
}`}
>
<h2 className="tw-mb-sm tw-font-semibold">{name}</h2>
<h3 className="tw-font-normal tw-max-w-sm">{description}</h3>
<h4 className="tw-my-sm">{price}</h4>
{buttons}

<div className="tw-py-sm ml-0">
{isEnterprise && <div className="tw-text-xl tw-font-semibold tw-mb-sm">Everything in Business, plus:</div>}
{features.map(node => (
<div
key={node.topic}
className="tw-px-0 bg-transparent border-0 tw-text-xl list-group-itemtw-justify-between"
>
<div className="tw-text-xl tw-font-semibold tw-flex tw-items-center">
<CheckIcon
className={`mr-2 ${isEnterprise ? 'tw-text-violet-400' : 'tw-text-vermillion-300'}`}
/>
<h5 className="tw-w-full">{node.topic}</h5>
</div>
))}
</ol>
<ul className="tw-ml-2xl tw-mb-xs">
{node?.features?.map(feature => (
<div key={feature}>
<PricingPlanFeature feature={SPOTLIGHT_FEATURE_INFO[feature]} tag="li" />
</div>
))}
</ul>
</div>
))}
</div>
)
}
</div>
)
67 changes: 36 additions & 31 deletions src/components/Pricing/PricingPlanFeature.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,48 @@
import { FunctionComponent } from 'react'

import CheckIcon from 'mdi-react/CheckIcon'
import QuestionMarkCircleOutlineIcon from 'mdi-react/QuestionMarkCircleOutlineIcon'
import classNames from 'classnames'
import InformationCircleOutlineIcon from 'mdi-react/InformationCircleOutlineIcon'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Tooltip from 'react-bootstrap/Tooltip'

import { FeatureInfo } from './interfaces'
import { breakpoints } from '@data'
import { useWindowWidth } from '@hooks'

import { FeatureInfo } from './data'
interface Props {
info: FeatureInfo
value: boolean
tag: 'li'
feature: FeatureInfo
tag: 'li' | 'h5'
className?: string
}

export const PricingPlanFeature: FunctionComponent<Props> = ({
info: { label, description },
value,
tag: Tag = 'li',
className = '',
}) =>
value ? (
<Tag className={`${className} tw-flex tw-justify-between`}>
<div className="tw-text-xl">
<CheckIcon className="mr-2 icon-inline text-success tw-inline" /> {label}
export const PricingPlanFeature: FunctionComponent<Props> = ({ feature, tag: Tag = 'li', className }) => {
const windowWidth = useWindowWidth()
const isMdOrDown = windowWidth < breakpoints.lg

return (
<Tag className={classNames(Tag === 'li' && 'tw-text-sm')}>
<div className="tw-flex tw-my-xxs">
<div className={classNames('tw-text-lg', className)}>{feature.label}</div>

{feature.description && (
<OverlayTrigger
placement="auto"
flip={true}
transition={false}
overlay={
<Tooltip id="tooltip" placement="right" className="tw-shadow-lg tw-opacity-100">
{feature.description}
</Tooltip>
}
>
{({ ref, ...triggerHandler }) => (
<span {...triggerHandler} ref={ref} className="tw-ml-xxs tw-my-auto tw-text-gray-300">
<InformationCircleOutlineIcon size={isMdOrDown ? 25 : 19} />
</span>
)}
</OverlayTrigger>
)}
</div>
{description && (
<OverlayTrigger
placement="auto"
flip={true}
transition={false}
overlay={<Tooltip id="tooltip">{description}</Tooltip>}
>
{({ ref, ...triggerHandler }) => (
<span {...triggerHandler} ref={ref} className="ml-2 tw-text-gray-400">
<QuestionMarkCircleOutlineIcon />
</span>
)}
</OverlayTrigger>
)}
</Tag>
) : null
)
}
Loading