Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support paygate for nonexistent-free -> limited-paid, for reverse proxies #23099

Closed
wants to merge 18 commits into from
Closed
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
86 changes: 59 additions & 27 deletions frontend/src/lib/components/PayGateMini/PayGateMini.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getProductIcon } from 'scenes/products/Products'

import {
AvailableFeature,
AvailableFeatureUnion,
BillingProductV2AddonType,
BillingProductV2Type,
BillingV2FeatureType,
Expand All @@ -36,6 +37,11 @@ export interface PayGateMiniProps {
docsLink?: string
}

const featuresWithLimitOnPaidUnlimitedOnTeams: AvailableFeatureUnion[] = [
AvailableFeature.ORGANIZATIONS_PROJECTS,
AvailableFeature.MANAGED_REVERSE_PROXY,
]

/** A sort of paywall for premium features.
*
* Simply shows its children when the feature is available,
Expand Down Expand Up @@ -63,7 +69,8 @@ export function PayGateMini({
const { billing, billingLoading } = useValues(billingLogic)
const { hideUpgradeModal } = useActions(upgradeModalLogic)

const scrollToProduct = !(featureInfo?.key === AvailableFeature.ORGANIZATIONS_PROJECTS && !isAddonProduct)
const scrollToProduct =
featureInfo?.key && !(featuresWithLimitOnPaidUnlimitedOnTeams.includes(featureInfo?.key) && !isAddonProduct)

useEffect(() => {
if (gateVariant) {
Expand Down Expand Up @@ -114,7 +121,7 @@ export function PayGateMini({
featureInfo={featureInfo}
onCtaClick={handleCtaClick}
billing={billing}
scrollToProduct={scrollToProduct}
scrollToProduct={!!scrollToProduct}
isAddonProduct={isAddonProduct}
/>
{docsLink && isCloudOrDev && (
Expand Down Expand Up @@ -207,46 +214,71 @@ const renderUsageLimitMessage = (
isAddonProduct?: boolean,
handleCtaClick?: () => void
): JSX.Element => {
if (featureAvailableOnOrg?.limit && gateVariant !== 'move-to-cloud') {
if (
(featureAvailableOnOrg?.limit || (!featureAvailableOnOrg && featureInfoOnNextPlan?.limit)) &&
gateVariant !== 'move-to-cloud'
) {
return (
<div>
<p>
You've reached your usage limit for{' '}
<Tooltip title={featureInfo.description}>
<span>
<b>{featureInfo.name}</b>
<IconInfo className="ml-0.5 text-muted" />
</span>
</Tooltip>
.
</p>
<p className="border border-border bg-bg-3000 rounded p-4">
<b>Your current plan limit:</b>{' '}
<span>
{featureAvailableOnOrg.limit} {featureAvailableOnOrg.unit}
</span>
</p>
{featureInfo.key === AvailableFeature.ORGANIZATIONS_PROJECTS && !isAddonProduct ? (
{featureAvailableOnOrg ? (
<>
<p>
You've reached your usage limit for{' '}
<Tooltip title={featureInfo.description}>
<span>
<b>{featureInfo.name}</b>
<IconInfo className="ml-0.5 text-muted" />
</span>
</Tooltip>
.
</p>
<p className="border border-border bg-bg-3000 rounded p-4">
<b>Your current plan limit:</b>{' '}
<span>
{featureAvailableOnOrg.limit} {featureAvailableOnOrg.unit}
</span>
</p>
</>
) : featuresWithLimitOnPaidUnlimitedOnTeams.includes(featureInfo.key) && !isAddonProduct ? (
<>
<p>
<Tooltip title={featureInfo.description}>
<span>
<b>{featureInfo.name}</b>
<IconInfo className="ml-0.5 text-muted" />
</span>
</Tooltip>{' '}
is only available on paid plans.
</p>
</>
) : null}
{(featureAvailableOnOrg || featuresWithLimitOnPaidUnlimitedOnTeams.includes(featureInfo.key)) &&
!isAddonProduct ? (
<>
<p>
Please enter your credit card details by subscribing to any product (eg. Product analytics
or Session replay) to create up to <b>{featureInfoOnNextPlan?.limit} projects</b>.
or Session replay) to create up to{' '}
<b>
{featureInfoOnNextPlan?.limit || 'unlimited'} {featureInfoOnNextPlan?.unit}
</b>
. You can set billing limits as low as $0 to control your spend.
</p>
<p className="italic text-xs text-muted mb-4">
Need unlimited projects? Check out the{' '}
Need unlimited {featureInfoOnNextPlan?.unit}s? Check out the{' '}
<Link to="/organization/billing?products=platform_and_support" onClick={handleCtaClick}>
Teams addon
</Link>
.
</p>
</>
) : featureFlags[FEATURE_FLAGS.SUBSCRIBE_TO_ALL_PRODUCTS] === 'test' &&
billing?.subscription_level === 'free' &&
!isAddonProduct ? (
<p>Upgrade to create more {featureInfo.name}</p>
) : isAddonProduct ? (
<p>
Please upgrade to the <b>{productWithFeature.name} addon</b> to create more{' '}
{featureInfoOnNextPlan?.unit}.
</p>
) : (
<p>
Upgrade your <b>{productWithFeature.name}</b> plan to create more {featureInfo.name}
Please upgrade your <b>{productWithFeature.name}</b> plan to create more {featureInfo.name}.
</p>
)}
</div>
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/lib/components/PayGateMini/payGateMiniLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ export const payGateMiniLogic = kea<payGateMiniLogicType>([
(billing) => {
// TODO(@zach): revisit this logic after subscribe to all products is released
// There are some features where we want to check the product first
const checkProductFirst = [AvailableFeature.ORGANIZATIONS_PROJECTS]
const checkProductFirst = [
AvailableFeature.ORGANIZATIONS_PROJECTS,
AvailableFeature.MANAGED_REVERSE_PROXY,
]

let foundProduct: BillingProductV2Type | BillingProductV2AddonType | undefined = undefined

if (checkProductFirst.includes(props.featureKey)) {
foundProduct = billing?.products?.find((product) =>
product.features?.some((f) => f.key === props.featureKey)
foundProduct = billing?.products?.find(
(product) =>
product.features?.some((f) => f.key === props.featureKey) &&
!(product.inclusion_only && billing.has_active_subscription)
)
}

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/lib/components/UpgradeModal/UpgradeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LemonModal } from '@posthog/lemon-ui'
import { LemonBanner, LemonModal } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'

Expand All @@ -18,10 +18,10 @@ export function UpgradeModal(): JSX.Element {
isGrandfathered={upgradeModalIsGrandfathered ?? undefined}
background={false}
>
<div className="pr-7">
You should have access to this feature already. If you are still seeing this modal, please let
us know 🙂
</div>
<LemonBanner type="error">
There's been an error retrieving your billing info. Please try again in a few minutes. If you
are still seeing this modal, please let us know.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏

</LemonBanner>
</PayGateMini>
</div>
</LemonModal>
Expand Down
40 changes: 23 additions & 17 deletions frontend/src/scenes/settings/project/ManagedReverseProxy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@ import { useActions, useValues } from 'kea'
import { Form } from 'kea-forms'
import { CodeSnippet, Language } from 'lib/components/CodeSnippet'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'

import { AvailableFeature } from '~/types'

import { proxyLogic, ProxyRecord } from './proxyLogic'

const MAX_PROXY_RECORDS = 3

export function ManagedReverseProxy(): JSX.Element {
const { formState, proxyRecords, proxyRecordsLoading } = useValues(proxyLogic)
const { showForm, deleteRecord } = useActions(proxyLogic)

const maxRecordsReached = proxyRecords.length >= MAX_PROXY_RECORDS
const { guardAvailableFeature } = useValues(upgradeModalLogic)

const recordsWithMessages = proxyRecords.filter((record) => !!record.message)

Expand Down Expand Up @@ -108,8 +106,8 @@ export function ManagedReverseProxy(): JSX.Element {
]

return (
<PayGateMini feature={AvailableFeature.MANAGED_REVERSE_PROXY}>
<div className="space-y-2">
<div className="space-y-2">
<PayGateMini feature={AvailableFeature.MANAGED_REVERSE_PROXY}>
{recordsWithMessages.map((r) => (
<LemonBanner type="warning" key={r.id}>
<LemonMarkdown>{`**${r.domain}**\n ${r.message}`}</LemonMarkdown>
Expand All @@ -124,20 +122,28 @@ export function ManagedReverseProxy(): JSX.Element {
}}
/>
{formState === 'collapsed' ? (
maxRecordsReached ? (
<LemonBanner type="info">
There is a maximum of {MAX_PROXY_RECORDS} records allowed per organization
</LemonBanner>
) : (
<LemonButton onClick={showForm} type="secondary" icon={<IconPlus />}>
New managed proxy
</LemonButton>
)
<LemonButton
onClick={() =>
guardAvailableFeature(
AvailableFeature.MANAGED_REVERSE_PROXY,
() => {
showForm()
},
{
currentUsage: proxyRecords.length,
}
)
}
type="secondary"
icon={<IconPlus />}
>
New managed proxy
</LemonButton>
) : (
<CreateRecordForm />
)}
</div>
</PayGateMini>
</PayGateMini>
</div>
)
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export enum AvailableFeature {
MANAGED_REVERSE_PROXY = 'managed_reverse_proxy',
}

type AvailableFeatureUnion = `${AvailableFeature}`
export type AvailableFeatureUnion = `${AvailableFeature}`

export enum ProductKey {
COHORTS = 'cohorts',
Expand Down
Loading