From 8a398d5060a828dcf0fad91d8f9b2f2e8f8bbc30 Mon Sep 17 00:00:00 2001 From: Vlad Keliy Date: Wed, 11 Jun 2025 09:57:57 +0200 Subject: [PATCH] refactor(plan) Extract and refactor generic plan utils --- .storybook/mock/user.ts | 23 ++++--- src/lib/analytics/amplitude/flow.svelte.ts | 4 +- src/lib/analytics/posthog/flow.svelte.ts | 4 +- src/lib/ui/app/PaymentForm/flow.ts | 4 +- .../PlanChangeDialog/PlanChangeDialog.svelte | 5 +- .../BreakdownTable/breakdown.ts | 14 ++--- .../ui/app/SubscriptionPlan/PlanButton.svelte | 22 +++---- src/lib/ui/app/SubscriptionPlan/api.ts | 17 ++---- src/lib/ui/app/SubscriptionPlan/index.ts | 18 +----- src/lib/ui/app/SubscriptionPlan/plans.ts | 55 ++++------------- .../ui/app/SubscriptionPlan/subscription.ts | 27 +++++---- src/lib/ui/app/SubscriptionPlan/utils.ts | 36 +++++------ src/lib/utils/index.ts | 2 +- src/lib/utils/object.ts | 4 ++ src/lib/utils/plans/index.ts | 16 +++++ src/lib/utils/plans/plans.ts | 60 +++++++++++++++++++ src/lib/utils/plans/products.ts | 20 +++++++ src/lib/utils/plans/subscription.ts | 32 ++++++++++ 18 files changed, 220 insertions(+), 143 deletions(-) create mode 100644 src/lib/utils/plans/index.ts create mode 100644 src/lib/utils/plans/plans.ts create mode 100644 src/lib/utils/plans/products.ts create mode 100644 src/lib/utils/plans/subscription.ts diff --git a/.storybook/mock/user.ts b/.storybook/mock/user.ts index 25a2fa1be..3ea2e5cd7 100644 --- a/.storybook/mock/user.ts +++ b/.storybook/mock/user.ts @@ -1,10 +1,7 @@ // import { getTodaysEnd } from '@/utils/dates' +import { checkIsBusinessPlan, Plan, Product } from '$lib/utils/plans/index.js' + import { getTodaysEnd } from '../../src/lib/utils/dates/index.js' -import { - SubscriptionPlan, - Product, - checkIsBusinessPlan, -} from '../../src/lib/ui/app/SubscriptionPlan/index.js' export type CurrentUser = null | { /** @default 42 */ @@ -147,14 +144,14 @@ export function mockUser(currentUser: CurrentUser) { if (pro || max || proPlus || businessPro || businessMax || custom || planName) { let name = planName - if (pro) name = SubscriptionPlan.PRO.key - else if (max) name = SubscriptionPlan.MAX.key - else if (proPlus) name = SubscriptionPlan.PRO_PLUS.key - else if (businessPro) name = SubscriptionPlan.BUSINESS_PRO.key - else if (businessMax) name = SubscriptionPlan.BUSINESS_MAX.key - else if (custom) name = SubscriptionPlan.CUSTOM.key - - const id = name && checkIsBusinessPlan({ name }) ? Product.SanAPI.id : Product.Sanbase.id + if (pro) name = Plan.PRO + else if (max) name = Plan.MAX + else if (proPlus) name = Plan.PRO_PLUS + else if (businessPro) name = Plan.BUSINESS_PRO + else if (businessMax) name = Plan.BUSINESS_MAX + else if (custom) name = Plan.CUSTOM + + const id = name && checkIsBusinessPlan(name) ? Product.SANAPI.id : Product.SANBASE.id subscriptions[0] = { status: trial ? 'TRIALING' : 'ACTIVE', diff --git a/src/lib/analytics/amplitude/flow.svelte.ts b/src/lib/analytics/amplitude/flow.svelte.ts index 7c0502c7d..c872f3e35 100644 --- a/src/lib/analytics/amplitude/flow.svelte.ts +++ b/src/lib/analytics/amplitude/flow.svelte.ts @@ -3,7 +3,7 @@ import * as amplitude from '@amplitude/analytics-browser' import { useCustomerCtx } from '$lib/ctx/customer/index.svelte.js' import { useUiCtx } from '$lib/ctx/ui/index.svelte.js' -import { SubscriptionPlan } from '$ui/app/SubscriptionPlan/plans.js' +import { Plan } from '$lib/utils/plans/index.js' export function useDebouncedFn void>( time: number, @@ -48,7 +48,7 @@ export function useAmplitudeFlow() { ) const updateUserSanbasePlan = useDebouncedFn(1000, (sanbase_plan?: string) => - setAmplitudeUserProperties({ sanbase_plan: sanbase_plan || SubscriptionPlan.FREE.key }), + setAmplitudeUserProperties({ sanbase_plan: sanbase_plan || Plan.FREE }), ) $effect(() => { diff --git a/src/lib/analytics/posthog/flow.svelte.ts b/src/lib/analytics/posthog/flow.svelte.ts index df2f047c5..776cd447a 100644 --- a/src/lib/analytics/posthog/flow.svelte.ts +++ b/src/lib/analytics/posthog/flow.svelte.ts @@ -2,7 +2,7 @@ import { posthog } from 'posthog-js' import { BROWSER } from 'esm-env' import { useCustomerCtx } from '$lib/ctx/customer/index.js' -import { SubscriptionPlan } from '$ui/app/SubscriptionPlan/plans.js' +import { Plan } from '$lib/utils/plans/index.js' import { useDebouncedFn } from '../amplitude/flow.svelte.js' @@ -22,7 +22,7 @@ export function usePosthogFlow() { (sanbase_plan?: string, featureAccessLevel?: string) => posthog.capture('$set', { $set: { - sanbase_plan: sanbase_plan || SubscriptionPlan.FREE.key, + sanbase_plan: sanbase_plan || Plan.FREE, feature_access_level: featureAccessLevel, }, }), diff --git a/src/lib/ui/app/PaymentForm/flow.ts b/src/lib/ui/app/PaymentForm/flow.ts index 59a60065d..a37f105b0 100644 --- a/src/lib/ui/app/PaymentForm/flow.ts +++ b/src/lib/ui/app/PaymentForm/flow.ts @@ -6,10 +6,10 @@ import { useStripeCtx } from '$lib/ctx/stripe/index.js' import { notification } from '$ui/core/Notifications/index.js' import { useCustomerCtx } from '$lib/ctx/customer/index.js' import { trackEvent } from '$lib/analytics/index.js' +import { getPlanDisplayName } from '$lib/utils/plans/index.js' import { mutateSubscribe } from './api.js' import { usePaymentFormCtx } from './state.js' -import { getPlanName } from '../SubscriptionPlan/utils.js' export type TPaymentFlowResult = undefined | API.ExtractData @@ -134,7 +134,7 @@ export function usePaymentFlow() { return Promise.reject('paymentMethod is missing') } - const planDisplayName = getPlanName(plan) + const planDisplayName = getPlanDisplayName(plan.name) const isConsumerPlan = !subscriptionPlan.$.formatted?.isBusiness const isEligibleForSanbaseTrial = isConsumerPlan && customer.$.isEligibleForSanbaseTrial diff --git a/src/lib/ui/app/PlanChangeDialog/PlanChangeDialog.svelte b/src/lib/ui/app/PlanChangeDialog/PlanChangeDialog.svelte index 38273711c..449f268cc 100644 --- a/src/lib/ui/app/PlanChangeDialog/PlanChangeDialog.svelte +++ b/src/lib/ui/app/PlanChangeDialog/PlanChangeDialog.svelte @@ -16,9 +16,10 @@ import { useCustomerCtx } from '$lib/ctx/customer/index.svelte.js' import { Query } from '$lib/api/executor.js' import { getFormattedMonthDayYear } from '$lib/utils/dates/index.js' + import { getPlanDisplayName } from '$lib/utils/plans/index.js' import { mutateUpdateSubscription } from './api.js' - import { getFormattedBillingPlan, getPlanName } from '../SubscriptionPlan/utils.js' + import { getFormattedBillingPlan } from '../SubscriptionPlan/utils.js' let { source = '', @@ -42,7 +43,7 @@ if (!primarySubscription) return if (loading) return - const planDisplayName = getPlanName(newPlan) + const planDisplayName = getPlanDisplayName(newPlan.name) loading = true Controller.lock() diff --git a/src/lib/ui/app/SubscriptionPlan/BreakdownTable/breakdown.ts b/src/lib/ui/app/SubscriptionPlan/BreakdownTable/breakdown.ts index 7d0700ca0..114b1e5f2 100644 --- a/src/lib/ui/app/SubscriptionPlan/BreakdownTable/breakdown.ts +++ b/src/lib/ui/app/SubscriptionPlan/BreakdownTable/breakdown.ts @@ -1,4 +1,4 @@ -import { SubscriptionPlan } from '../plans.js' +import { Plan } from '$lib/utils/plans/index.js' export type TBreakdownFeature = { name: string @@ -231,7 +231,7 @@ export const SubscriptionPlanBreakdown: Record< string, undefined | Record > = { - [SubscriptionPlan.FREE.key]: { + [Plan.FREE]: { 'Browser tabs': 'Up to 4', 'Bi-weekly report': false, 'Pro Insights': false, @@ -273,7 +273,7 @@ export const SubscriptionPlanBreakdown: Record< 'Direct technical support': false, }, - [SubscriptionPlan.PRO.key]: { + [Plan.PRO]: { 'Dedicated account manager': false, 'Custom onboarding & education': false, @@ -298,7 +298,7 @@ export const SubscriptionPlanBreakdown: Record< 'Direct technical support': false, }, - [SubscriptionPlan.MAX.key]: { + [Plan.MAX]: { 'Custom Alerts': 20, 'Historical data restriction': '1 year', @@ -315,7 +315,7 @@ export const SubscriptionPlanBreakdown: Record< 'Private queries': 'No', }, - [SubscriptionPlan.CUSTOM.key]: { + [Plan.CUSTOM]: { 'Multi-seat account': 'Custom', 'Historical data restriction': 'No restriction', @@ -335,7 +335,7 @@ export const SubscriptionPlanBreakdown: Record< 'Custom Alerts': 'Custom', }, - [SubscriptionPlan.BUSINESS_PRO.key]: { + [Plan.BUSINESS_PRO]: { 'Multi-seat account': false, 'Historical data restriction': 'Last 2 years', @@ -363,7 +363,7 @@ export const SubscriptionPlanBreakdown: Record< 'Custom data integratoins': false, }, - [SubscriptionPlan.BUSINESS_MAX.key]: { + [Plan.BUSINESS_MAX]: { 'Multi-seat account': '3 seats', 'Historical data restriction': 'No restriction', diff --git a/src/lib/ui/app/SubscriptionPlan/PlanButton.svelte b/src/lib/ui/app/SubscriptionPlan/PlanButton.svelte index 67bd094d2..e7ea35941 100644 --- a/src/lib/ui/app/SubscriptionPlan/PlanButton.svelte +++ b/src/lib/ui/app/SubscriptionPlan/PlanButton.svelte @@ -4,18 +4,18 @@ import { trackEvent } from '$lib/analytics/index.js' import { useCustomerCtx } from '$lib/ctx/customer/index.js' import { onSupportClick } from '$lib/utils/support.js' - import { - BUSINESS_PLANS, - CONSUMER_PLANS, - SubscriptionPlan, - } from '$ui/app/SubscriptionPlan/plans.js' import { checkIsAlternativeBillingPlan, checkIsCurrentPlan, - getPlanName, } from '$ui/app/SubscriptionPlan/utils.js' import Button from '$ui/core/Button/index.js' import { cn } from '$ui/utils/index.js' + import { + checkIsBusinessPlan, + checkIsConsumerPlan, + getPlanDisplayName, + Plan, + } from '$lib/utils/plans/index.js' import { useSubscriptionPlanButtonCtx } from './ctx.js' @@ -34,8 +34,8 @@ const { customer } = useCustomerCtx() let source = $derived(_source + '_plan_button') - let isBusinessPlan = $derived(BUSINESS_PLANS.has(plan.name)) - let isConsumerPlan = $derived(CONSUMER_PLANS.has(plan.name)) + let isBusinessPlan = $derived(checkIsBusinessPlan(plan.name)) + let isConsumerPlan = $derived(checkIsConsumerPlan(plan.name)) let isCurrentPlan = $derived(checkIsCurrentPlan(customer.$.plan, plan)) let isSameProductPlan = $derived( @@ -90,7 +90,7 @@ Your current plan {/if} -{:else if plan.name === SubscriptionPlan.FREE.key} +{:else if plan.name === Plan.FREE} -{:else if plan.name === SubscriptionPlan.CUSTOM.key} +{:else if plan.name === Plan.CUSTOM} {:else if isConsumerPlan && (customer.$.isEligibleForSanbaseTrial || isAnonymous)}