Files
pulse/lib/plans.ts
Usman Baig 7ba5e063ca feat: add free plan to pricing page and enforce 1-site limit
Show the free tier (€0, 1 site, 5k pageviews, 6 months retention)
as the first card on the pricing page. Enforce a 1-site limit for
free plan users in the frontend.
2026-03-13 21:28:04 +01:00

82 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Shared plan and traffic tier definitions for pricing and billing (Change plan).
* Backend supports plan_id solo, team, business and limit 10k10M; month/year interval.
*/
export const PLAN_ID_SOLO = 'solo'
export const PLAN_ID_TEAM = 'team'
export const PLAN_ID_BUSINESS = 'business'
/** Sites limit per plan. */
export function getSitesLimitForPlan(planId: string | null | undefined): number | null {
if (!planId || planId === 'free') return 1
switch (planId) {
case 'solo': return 1
case 'team': return 5
case 'business': return 10
default: return null
}
}
/** Traffic tiers available for Solo plan (pageview limits). */
export const TRAFFIC_TIERS = [
{ label: '10k', value: 10000 },
{ label: '50k', value: 50000 },
{ label: '100k', value: 100000 },
{ label: '250k', value: 250000 },
{ label: '500k', value: 500000 },
{ label: '1M', value: 1000000 },
{ label: '2.5M', value: 2500000 },
{ label: '5M', value: 5000000 },
{ label: '10M', value: 10000000 },
] as const
export function getTierIndexForLimit(limit: number): number {
const idx = TRAFFIC_TIERS.findIndex((t) => t.value === limit)
return idx >= 0 ? idx : 2
}
export function getLimitForTierIndex(index: number): number {
if (index < 0 || index >= TRAFFIC_TIERS.length) return 100000
return TRAFFIC_TIERS[index].value
}
/** Maximum data retention (months) allowed per plan. */
export function getMaxRetentionMonthsForPlan(planId: string | null | undefined): number {
switch (planId) {
case 'business': return 36
case 'team': return 24
case 'solo': return 12
default: return 6
}
}
/** Selectable retention options (months) for the given plan. */
export function getRetentionOptionsForPlan(planId: string | null | undefined): { label: string; value: number }[] {
const base = [
{ label: '1 month', value: 1 },
{ label: '3 months', value: 3 },
{ label: '6 months', value: 6 },
]
const solo = [...base, { label: '1 year', value: 12 }]
const team = [...solo, { label: '2 years', value: 24 }]
const business = [...team, { label: '3 years', value: 36 }]
switch (planId) {
case 'business': return business
case 'team': return team
case 'solo': return solo
default: return base
}
}
/** Human-readable label for a retention value in months. */
export function formatRetentionMonths(months: number): string {
if (months === 0) return 'Forever'
if (months === 1) return '1 month'
if (months < 12) return `${months} months`
const years = months / 12
if (Number.isInteger(years)) return years === 1 ? '1 year' : `${years} years`
return `${months} months`
}