diff --git a/frontend/src/layout/navigation/TopBar/AccountPopover.tsx b/frontend/src/layout/navigation/TopBar/AccountPopover.tsx index b7cdf5940471e..61ada26e4ed65 100644 --- a/frontend/src/layout/navigation/TopBar/AccountPopover.tsx +++ b/frontend/src/layout/navigation/TopBar/AccountPopover.tsx @@ -15,9 +15,11 @@ import { import { LemonButtonPropsBase } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' +import { FEATURE_FLAGS } from 'lib/constants' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { ProfilePicture } from 'lib/lemon-ui/ProfilePicture' import { UploadedLogo } from 'lib/lemon-ui/UploadedLogo' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { inviteLogic } from 'scenes/settings/organization/inviteLogic' import { ThemeSwitcher } from 'scenes/settings/user/ThemeSwitcher' @@ -212,6 +214,7 @@ export function AccountPopoverOverlay(): JSX.Element { const { openSidePanel } = useActions(sidePanelStateLogic) const { preflight, isCloudOrDev, isCloud } = useValues(preflightLogic) const { closeAccountPopover } = useActions(navigationLogic) + const { featureFlags } = useValues(featureFlagLogic) return ( <> @@ -223,12 +226,16 @@ export function AccountPopoverOverlay(): JSX.Element { {isCloudOrDev ? ( } fullWidth data-attr="top-menu-item-billing" > - Billing + {featureFlags[FEATURE_FLAGS.BILLING_USAGE_DASHBOARD] ? 'Billing & Usage' : 'Billing'} ) : null} diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index aa53d7dcd0dca..7dcec3058115a 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -233,6 +233,7 @@ export const FEATURE_FLAGS = { BILLING_SKIP_FORECASTING: 'billing-skip-forecasting', // owner: @zach EXPERIMENT_STATS_V2: 'experiment-stats-v2', // owner: @danielbachhuber #team-experiments WEB_ANALYTICS_PERIOD_COMPARISON: 'web-analytics-period-comparison', // owner: @rafaeelaudibert #team-web-analytics + BILLING_USAGE_DASHBOARD: 'billing-usage-dashboard', // owner: @pato WEB_ANALYTICS_CONVERSION_GOAL_FILTERS: 'web-analytics-conversion-goal-filters', // owner: @rafaeelaudibert #team-web-analytics CDP_ACTIVITY_LOG_NOTIFICATIONS: 'cdp-activity-log-notifications', // owner: #team-cdp COOKIELESS_SERVER_HASH_MODE_SETTING: 'cookieless-server-hash-mode-setting', // owner: @robbie-c #team-web-analytics diff --git a/frontend/src/scenes/appScenes.ts b/frontend/src/scenes/appScenes.ts index cf58492e72677..e2e63409fc5fe 100644 --- a/frontend/src/scenes/appScenes.ts +++ b/frontend/src/scenes/appScenes.ts @@ -62,6 +62,7 @@ export const appScenes: Record any> = { [Scene.Signup]: () => import('./authentication/signup/SignupContainer'), [Scene.InviteSignup]: () => import('./authentication/InviteSignup'), [Scene.Billing]: () => import('./billing/Billing'), + [Scene.BillingSection]: () => import('./billing/BillingSection'), [Scene.BillingAuthorizationStatus]: () => import('./billing/AuthorizationStatus'), [Scene.Login]: () => import('./authentication/Login'), [Scene.Login2FA]: () => import('./authentication/Login2FA'), diff --git a/frontend/src/scenes/billing/BillingOverview.tsx b/frontend/src/scenes/billing/BillingOverview.tsx new file mode 100644 index 0000000000000..58770ba9ccab2 --- /dev/null +++ b/frontend/src/scenes/billing/BillingOverview.tsx @@ -0,0 +1,10 @@ +import { LemonBanner } from '@posthog/lemon-ui' + +export function BillingOverview(): JSX.Element { + return ( +
+

Billing Overview

+ Overview section under construction +
+ ) +} diff --git a/frontend/src/scenes/billing/BillingSection.tsx b/frontend/src/scenes/billing/BillingSection.tsx new file mode 100644 index 0000000000000..31e5fa6a08914 --- /dev/null +++ b/frontend/src/scenes/billing/BillingSection.tsx @@ -0,0 +1,62 @@ +import './Billing.scss' + +import { LemonButton } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { router } from 'kea-router' +import { useEffect } from 'react' +import { SceneExport } from 'scenes/sceneTypes' +import { urls } from 'scenes/urls' + +import { billingLogic } from './billingLogic' +import { BillingOverview } from './BillingOverview' +import { BillingUsage } from './BillingUsage' + +export const scene: SceneExport = { + component: BillingSection, + logic: billingLogic, +} + +export function BillingSection(): JSX.Element { + const { location } = useValues(router) + const { push } = useActions(router) + + const activeSection = location.pathname.endsWith('/usage') ? 'usage' : 'overview' + + useEffect(() => { + if (location.pathname === '/organization/billing') { + push(urls.organizationBillingSection('overview')) + } + }, [location.pathname]) + + return ( +
+
+
    +
  • + push(urls.organizationBillingSection('overview'))} + active={activeSection === 'overview'} + size="small" + fullWidth + > + Overview + +
  • +
  • + push(urls.organizationBillingSection('usage'))} + active={activeSection === 'usage'} + size="small" + fullWidth + > + Usage + +
  • +
+
+
+ {activeSection === 'overview' ? : } +
+
+ ) +} diff --git a/frontend/src/scenes/billing/BillingUsage.tsx b/frontend/src/scenes/billing/BillingUsage.tsx new file mode 100644 index 0000000000000..b93e51dbd5acb --- /dev/null +++ b/frontend/src/scenes/billing/BillingUsage.tsx @@ -0,0 +1,10 @@ +import { LemonBanner } from '@posthog/lemon-ui' + +export function BillingUsage(): JSX.Element { + return ( +
+

Usage Details

+ Usage section under construction +
+ ) +} diff --git a/frontend/src/scenes/billing/types.ts b/frontend/src/scenes/billing/types.ts index a5dc2559d9941..151b67d1bd5bf 100644 --- a/frontend/src/scenes/billing/types.ts +++ b/frontend/src/scenes/billing/types.ts @@ -12,3 +12,5 @@ export type BillingGaugeItemType = { value: number top: boolean } + +export type BillingSectionId = 'overview' | 'usage' diff --git a/frontend/src/scenes/sceneTypes.ts b/frontend/src/scenes/sceneTypes.ts index b64113b02aacc..3b9f213299686 100644 --- a/frontend/src/scenes/sceneTypes.ts +++ b/frontend/src/scenes/sceneTypes.ts @@ -60,6 +60,7 @@ export enum Scene { AsyncMigrations = 'AsyncMigrations', DeadLetterQueue = 'DeadLetterQueue', Billing = 'Billing', + BillingSection = 'BillingSection', BillingAuthorizationStatus = 'BillingAuthorizationStatus', SavedInsights = 'SavedInsights', ToolbarLaunch = 'ToolbarLaunch', diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 2ba48cb64e286..614322b064931 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -19,6 +19,7 @@ import { ReplayTabs, } from '~/types' +import { BillingSectionId } from './billing/types' export const emptySceneParams = { params: {}, searchParams: {}, hashParams: {} } export const preloadedScenes: Record = { @@ -375,6 +376,11 @@ export const sceneConfigurations: Record = { organizationBased: true, defaultDocsPath: '/pricing', }, + [Scene.BillingSection]: { + name: 'Billing', + hideProjectNotice: true, + organizationBased: true, + }, [Scene.BillingAuthorizationStatus]: { hideProjectNotice: true, organizationBased: true, @@ -592,6 +598,7 @@ export const routes: Record = { [urls.max()]: Scene.Max, [urls.projectCreateFirst()]: Scene.ProjectCreateFirst, [urls.organizationBilling()]: Scene.Billing, + [urls.organizationBillingSection(':section' as BillingSectionId)]: Scene.BillingSection, [urls.billingAuthorizationStatus()]: Scene.BillingAuthorizationStatus, [urls.organizationCreateFirst()]: Scene.OrganizationCreateFirst, [urls.organizationCreationConfirm()]: Scene.OrganizationCreationConfirm, diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index c3dda8bda6fcf..38647a1e9389b 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -20,10 +20,10 @@ import { SDKKey, } from '~/types' +import { BillingSectionId } from './billing/types' import { OnboardingStepKey } from './onboarding/onboardingLogic' import { SettingId, SettingLevelId, SettingSectionId } from './settings/types' import { SurveysTabs } from './surveys/surveysLogic' - /** * To add a new URL to the front end: * - add a URL function here @@ -210,6 +210,8 @@ export const urls = { // Cloud only organizationBilling: (products?: ProductKey[]): string => `/organization/billing${products && products.length ? `?products=${products.join(',')}` : ''}`, + organizationBillingSection: (section: BillingSectionId = 'overview'): string => + combineUrl(`/organization/billing/${section}`).url, billingAuthorizationStatus: (): string => `/billing/authorization_status`, // Self-hosted only instanceStatus: (): string => '/instance/status',