Skip to content

Conversation

@zlwaterfield
Copy link
Contributor

@zlwaterfield zlwaterfield commented May 23, 2025

Important

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Changes

Adding new platform addons. Main changes

  • legacy product support
  • more consistent plan nameing for free/paid
  • new addon support
  • some misc clean up

This PR should be shipped first. Then billing then posthog.com

See related PRs

Did you write or update any docs for this change?

  • I've added or updated the docs
  • I've reached out for help from the docs team
  • No docs needed for this change

How did you test this code?

@zlwaterfield zlwaterfield self-assigned this May 23, 2025
@zlwaterfield zlwaterfield marked this pull request as ready for review May 23, 2025 23:07
Comment on lines 429 to +435
lemonToast.success('Your trial has been activated!')
} catch (e) {
lemonToast.error('There was an error activating your trial. Please try again or contact support.')
} finally {
await breakpoint(400)
window.location.reload()
} catch (e) {
lemonToast.error('There was an error activating your trial. Please try again or contact support.')
actions.setTrialLoading(false)
actions.setTrialModalOpen(false)
actions.loadBilling()
Copy link
Contributor

Choose a reason for hiding this comment

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

The window.location.reload() call is placed before the error handling block, which means if an error occurs during trial activation, the page will reload before the error handling code executes. This prevents the error toast from displaying and proper cleanup from occurring.

Consider restructuring this to ensure error handling works correctly:

try {
    await api.create('api/billing/trials/activate', {
        target: props.product.type,
    })
    lemonToast.success('Your trial has been activated!')
    await breakpoint(400)
    window.location.reload() // Only reload on success
} catch (e) {
    lemonToast.error('There was an error activating your trial. Please try again or contact support.')
    actions.setTrialLoading(false)
    actions.loadBilling()
}

This ensures the page only reloads on successful trial activation, while error cases are properly handled.

Suggested change
lemonToast.success('Your trial has been activated!')
} catch (e) {
lemonToast.error('There was an error activating your trial. Please try again or contact support.')
} finally {
await breakpoint(400)
window.location.reload()
} catch (e) {
lemonToast.error('There was an error activating your trial. Please try again or contact support.')
actions.setTrialLoading(false)
actions.setTrialModalOpen(false)
actions.loadBilling()
try {
await api.create('api/billing/trials/activate', {
target: props.product.type,
})
lemonToast.success('Your trial has been activated!')
await breakpoint(400)
window.location.reload()
} catch (e) {
lemonToast.error('There was an error activating your trial. Please try again or contact support.')
actions.setTrialLoading(false)
actions.loadBilling()
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

PR Summary

This PR introduces platform addons and billing plan updates across PostHog's frontend, focusing on standardizing plan naming and supporting legacy products.

  • Renamed plans from 'Totally free' to 'Free' and 'Ridiculously cheap' to 'Pay-as-you-go' across all billing components and fixtures
  • Added new platform addons including Teams with features like priority support, unlimited projects, and SSO enforcement
  • Added legacy_product flag in types and special handling for legacy team add-ons with warning banners
  • Added isSubscribedToAnotherAddon check to prevent multiple addon subscriptions
  • Introduced new Boost and Scale billing plans while maintaining legacy Teams plan support

14 file(s) reviewed, 3 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +360 to +362
product.addons.find((addon) => addon.legacy_product && addon.subscribed)
?.name
}{' '}
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Multiple find() calls on the same condition. Consider storing the result in a variable

}
return true
})
.map((addon, i) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Using array index as React key may cause issues with list rendering if items are reordered

Comment on lines 226 to 228
const parentProduct = billing.products.find((product: any) =>
product.addons.find((a: BillingProductV2AddonType) => a.type === addon.type)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Type any used for product. Should explicitly type this as BillingProductV2Type to ensure type safety

Suggested change
const parentProduct = billing.products.find((product: any) =>
product.addons.find((a: BillingProductV2AddonType) => a.type === addon.type)
)
const parentProduct = billing.products.find((product: BillingProductV2Type) =>
product.addons?.find((a: BillingProductV2AddonType) => a.type === addon.type)
)

@posthog-bot
Copy link
Contributor

📸 UI snapshots have been updated

4 snapshot changes in total. 0 added, 4 modified, 0 deleted:

  • chromium: 0 added, 4 modified, 0 deleted (diff for shard 1)
  • webkit: 0 added, 0 modified, 0 deleted

Triggered by this commit.

👉 Review this PR's diff of snapshots.

@github-actions
Copy link
Contributor

github-actions bot commented May 23, 2025

Size Change: 0 B

Total Size: 3.72 MB

ℹ️ View Unchanged
Filename Size
frontend/dist/toolbar.js 3.72 MB

compressed-size-action

Copy link
Contributor

@pawel-cebula pawel-cebula left a comment

Choose a reason for hiding this comment

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

Looks great! Very happy to see "Pay-as-you-go" instead of "Ridiculously cheap".

Left a couple of minor suggestions and have one overall feedback on the billing page UI/UX. Neither is a blocker for merging though.

Currently, it's not clear from the UI that the platform add-ons are incremental and all the features from the previous ones are included. We could e.g.

  • State this more clearly on each card ("Everything included in Scale plus: ...")
  • If you are subscribed to a platform add-on, make the other add-on cards a bit less opaque, lighter grey, kind of like a disabled button
  • Indicate progression between different add-on tiers visually
    • Right now they have the same icon - maybe use a different one and show it in different quantity depending on the add-on plan? Maybe like army pins - 1/2/3/4 star pin/badge
    • Instead of full width boxes, use a 3-4 columns view (probably tight, esp. with 4), like on pricing pages
    • Probably too weird (and probably fits better for posthog.com pricing pages, if at all) but maybe we could get a hedgehog version of the wrestling guy reaction meme

Might be a good follow up task for @yasen-posthog to experiment with!

image

Comment on lines -96 to +128
getDescription: (billingPlan: BillingPlan, scrollToProduct: (productType: string) => void) => (
getDescription: (_billingPlan: BillingPlan, scrollToProduct: (productType: string) => void) => (
<p>
If you're growing like crazy, you might want to check out the{' '}
{billingPlan !== BillingPlan.Teams ? (
<>
{scrollToProduct ? (
<>
<Link onClick={() => scrollToProduct('teams')}>Teams</Link>
{' or '}
</>
) : (
'Teams or '
)}
</>
) : null}
{scrollToProduct ? <Link onClick={() => scrollToProduct('enterprise')}>Enterprise</Link> : 'Enterprise'}{' '}
plan.
If you're growing like crazy, you might want to check out our{' '}
{scrollToProduct ? (
<Link onClick={() => scrollToProduct('platform_and_support')}>Platform add-ons</Link>
) : (
'Platform add-ons'
)}
.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we still check if they already have the platform add-on to avoid showing it,at least if they're already on Enterprise? Small edge case though.

Comment on lines 418 to 427
.filter((addon) => {
if (
product.type === 'platform_and_support' &&
addon.legacy_product &&
!addon.subscribed
) {
return false
}
return true
})
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: move the if to the existing filter to avoid looping twice

I'm also a fan of having things like this in the logic, via something like e.g. visibleAddons selector - makes reading these components easier

Comment on lines +26 to +27
[BillingPlan.Boost]: planTeams, // TODO: Add Boost badge
[BillingPlan.Scale]: planTeams, // TODO: Add Scale badge
Copy link
Contributor

Choose a reason for hiding this comment

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

Good to request this art work asap as it might take a while

@zlwaterfield zlwaterfield merged commit 2ee43f3 into master May 26, 2025
95 of 96 checks passed
@zlwaterfield zlwaterfield deleted the zach/new-platform-addons branch May 26, 2025 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants