diff --git a/src/app/pricing/pricing-calculator.tsx b/src/app/pricing/pricing-calculator.tsx new file mode 100644 index 00000000..72d88ee2 --- /dev/null +++ b/src/app/pricing/pricing-calculator.tsx @@ -0,0 +1,355 @@ +'use client'; + +import { ArrowRight, Calculator, ChevronDown, ChevronUp, Sparkles } from 'lucide-react'; +import Link from 'next/link'; +import { useEffect, useMemo, useRef, useState } from 'react'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { track } from '@/lib/analytics/posthog'; +import { cn } from '@/lib/utils'; + +import { + CalculatorInputs, + COMPETITOR_PER_DRIVER_USD, + computeUnits, + recommendPlan, +} from './pricing-data'; + +const DEFAULT_INPUTS: CalculatorInputs = { + drivers: 5, + vehicles: 5, + orders: 200, + users: 2, + contacts: 100, + places: 100, + vendors: 0, + serviceRates: 3, + serviceAreas: 1, + zones: 0, + webhooks: 0, + apiKeys: 1, +}; + +type Field = { + key: keyof CalculatorInputs; + label: string; + min: number; + max: number; + step: number; + helper?: string; + free?: boolean; +}; + +const PRIMARY_FIELDS: Field[] = [ + { key: 'drivers', label: 'Drivers', min: 0, max: 200, step: 1, free: true, helper: 'Free — never billed.' }, + { key: 'orders', label: 'Orders per month', min: 0, max: 10000, step: 25, free: true, helper: 'Free — never billed.' }, + { key: 'vehicles', label: 'Vehicles', min: 0, max: 200, step: 1 }, + { key: 'users', label: 'Dispatchers / Users', min: 1, max: 100, step: 1, helper: '5 units each' }, +]; + +const ADVANCED_FIELDS: Field[] = [ + { key: 'contacts', label: 'Customer contacts (new this month)', min: 0, max: 5000, step: 10 }, + { key: 'places', label: 'Delivery addresses (new this month)', min: 0, max: 5000, step: 10 }, + { key: 'vendors', label: 'Vendors', min: 0, max: 200, step: 1 }, + { key: 'serviceRates', label: 'Service rate configurations', min: 0, max: 50, step: 1 }, + { key: 'serviceAreas', label: 'Service areas', min: 0, max: 50, step: 1 }, + { key: 'zones', label: 'Zones', min: 0, max: 100, step: 1 }, + { key: 'webhooks', label: 'Webhook endpoints', min: 0, max: 50, step: 1, helper: '5 units each' }, + { key: 'apiKeys', label: 'API keys', min: 0, max: 50, step: 1 }, +]; + +type Props = { + billing: 'monthly' | 'annual'; +}; + +export default function PricingCalculator({ billing }: Props) { + const [inputs, setInputs] = useState(DEFAULT_INPUTS); + const [advancedOpen, setAdvancedOpen] = useState(false); + const lastTrackedUnits = useRef(null); + + const { total, breakdown } = useMemo(() => computeUnits(inputs), [inputs]); + const recommendation = useMemo(() => recommendPlan(total, billing), [total, billing]); + + // Competitor comparison anchored on driver count. + const competitorMonthly = inputs.drivers * COMPETITOR_PER_DRIVER_USD; + const fleetbaseMonthly = + billing === 'annual' ? recommendation.totalCost * 12 / 12 : recommendation.totalCost; + const monthlySavings = Math.max(0, competitorMonthly - fleetbaseMonthly); + const yearlySavings = monthlySavings * 12; + + // Debounced analytics emission on settle. + useEffect(() => { + const t = setTimeout(() => { + if (lastTrackedUnits.current === total) return; + lastTrackedUnits.current = total; + track('pricing_calculator_changed', { + units: total, + derived_price: recommendation.totalCost, + }); + }, 500); + return () => clearTimeout(t); + }, [total, recommendation.totalCost]); + + const setField = (key: keyof CalculatorInputs, value: number) => { + setInputs((prev) => ({ ...prev, [key]: value })); + }; + + return ( +
+
+
+
+ + Pricing Calculator +
+

+ Tell us about your operation. We'll find your plan. +

+

+ Drivers and orders are always free. + Adjust the numbers below to see your real bill — and what you'd pay on a typical + per-driver competitor. +

+
+ +
+ {/* Inputs */} + + + {PRIMARY_FIELDS.map((field) => ( + setField(field.key, v)} + /> + ))} + + + + {advancedOpen && ( +
+ {ADVANCED_FIELDS.map((field) => ( + setField(field.key, v)} + /> + ))} +
+ )} +
+
+ + {/* Output */} +
+ {/* Usage */} + + +
+
Your monthly usage
+
+ {total.toLocaleString()}{' '} + units +
+
+ {breakdown.length > 0 ? ( +
+ {breakdown.map((b) => ( +
+ + {b.label} ×{b.quantity} + + {b.units.toLocaleString()} units +
+ ))} +
+ Orders ×{inputs.orders.toLocaleString()} + FREE +
+
+ Drivers ×{inputs.drivers} + FREE +
+
+ ) : ( +
No billable usage yet — start with at least 1 user or vehicle.
+ )} +
+
+ + {/* Recommended plan */} + +
+ Your recommended plan +
+ +
+
{recommendation.plan.name}
+
+ ${recommendation.totalCost.toLocaleString(undefined, { maximumFractionDigits: 0 })} + /mo +
+
+ {billing === 'annual' && recommendation.fits && ( +
+ Billed annually (${(recommendation.totalCost * 12).toLocaleString()}/yr) +
+ )} +
+ {recommendation.fits ? ( + <> + Includes {recommendation.plan.units.toLocaleString()} units —{' '} + + {(recommendation.plan.units - total).toLocaleString()} units headroom + . + + ) : ( + <> + Largest plan ({recommendation.plan.units.toLocaleString()} units) plus{' '} + {recommendation.overageUnits.toLocaleString()} overage units at ${recommendation.plan.overage}/unit.{' '} + Talk to us about a custom Enterprise plan → + + )} +
+ +
+
+ + {/* Competitor comparison */} + {inputs.drivers > 0 && monthlySavings > 0 && ( + + +
+ +
+ Compared to a typical per-driver TMS +
+
+
+
+
Per-driver TMS
+
+ ${competitorMonthly.toLocaleString()} + /mo +
+
+ {inputs.drivers} × ${COMPETITOR_PER_DRIVER_USD}/driver +
+
+
+
Fleetbase
+
+ ${fleetbaseMonthly.toLocaleString(undefined, { maximumFractionDigits: 0 })} + /mo +
+
+ {recommendation.plan.name}, all-in +
+
+
+
+
+ Save ${monthlySavings.toLocaleString(undefined, { maximumFractionDigits: 0 })}/mo +
+
+ That's ${yearlySavings.toLocaleString(undefined, { maximumFractionDigits: 0 })} a year +
+
+

+ Benchmark of ${COMPETITOR_PER_DRIVER_USD}/driver/mo is the midpoint of published rates + from Detrack, OptimoRoute, and Track-POD ($29–$99). Competitors typically charge + separately for orders, integrations, and users. +

+
+
+ )} +
+
+
+
+ ); +} + +function CalculatorRow({ + field, + value, + onChange, +}: { + field: Field; + value: number; + onChange: (n: number) => void; +}) { + // Skip the inline helper for "free" fields — the FREE badge says it all. + const showHelper = field.helper && !field.free; + return ( +
+
+ + { + const n = Number(e.target.value); + if (Number.isFinite(n)) onChange(Math.max(field.min, Math.min(field.max, Math.round(n)))); + }} + className="w-16 text-right text-sm border rounded px-2 py-0.5 bg-background" + /> +
+ onChange(Number(e.target.value))} + className={cn( + 'w-full accent-primary cursor-pointer h-1.5', + field.free && 'opacity-90' + )} + /> +
+ ); +} diff --git a/src/app/pricing/pricing-client.tsx b/src/app/pricing/pricing-client.tsx index 8278ba37..41e4510a 100644 --- a/src/app/pricing/pricing-client.tsx +++ b/src/app/pricing/pricing-client.tsx @@ -1,192 +1,35 @@ 'use client'; -import { -ArrowRight, Building2, - Check, ChevronDown, ChevronUp, Globe, Key, Layers, MapPin, Package, - Receipt, Server, Truck, User, UserRound, -Users, Webhook, X, Zap, } from 'lucide-react'; +import { ArrowRight, Check, ChevronDown, ChevronUp, Server, X, Zap } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '@/components/ui/accordion'; import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardFooter,CardHeader, CardTitle } from '@/components/ui/card'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; import { track } from '@/lib/analytics/posthog'; import { cn } from '@/lib/utils'; -// ─── Cloud Pricing Tiers ────────────────────────────────────────────────────── -const CLOUD_TIERS = [ - { - name: 'Micro', - monthlyPrice: 25, - annualPrice: 20, - units: 100, - overage: 0.75, - description: 'Individuals and very small teams.', - featured: true, - highlight: false, - badge: null, - }, - { - name: 'Lite', - monthlyPrice: 50, - annualPrice: 40, - units: 180, - overage: 0.75, - description: 'Small teams getting started.', - featured: true, - highlight: false, - badge: null, - }, - { - name: 'Essential', - monthlyPrice: 100, - annualPrice: 80, - units: 240, - overage: 0.75, - description: 'Growing operations with more to manage.', - featured: true, - highlight: false, - badge: null, - }, - { - name: 'Starter', - monthlyPrice: 200, - annualPrice: 160, - units: 300, - overage: 0.75, - description: 'Established small teams scaling up.', - featured: true, - highlight: true, - badge: 'Most Popular', - }, - { - name: 'Starter Plus', - monthlyPrice: 300, - annualPrice: 240, - units: 500, - overage: 0.65, - description: 'Enhanced capacity at a lower unit cost.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Scale', - monthlyPrice: 400, - annualPrice: 320, - units: 800, - overage: 0.55, - description: 'High-volume operational teams.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Scale Plus', - monthlyPrice: 500, - annualPrice: 400, - units: 1200, - overage: 0.45, - description: 'Serious scale with a lower unit cost.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Pro', - monthlyPrice: 600, - annualPrice: 480, - units: 1700, - overage: 0.40, - description: 'Power teams running complex operations.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Pro Plus', - monthlyPrice: 700, - annualPrice: 560, - units: 2300, - overage: 0.35, - description: 'High-throughput pro operations.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Elite', - monthlyPrice: 800, - annualPrice: 640, - units: 3000, - overage: 0.30, - description: 'Enterprise-grade volume at high velocity.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Elite Plus', - monthlyPrice: 900, - annualPrice: 720, - units: 3800, - overage: 0.25, - description: 'Maximum capacity before Enterprise.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Enterprise', - monthlyPrice: 1000, - annualPrice: 800, - units: 5000, - overage: 0.20, - description: 'Full enterprise scale with the lowest unit cost.', - featured: false, - highlight: false, - badge: null, - }, - { - name: 'Enterprise Plus', - monthlyPrice: 1500, - annualPrice: 1200, - units: 7500, - overage: 0.15, - description: 'Largest cloud plan. Maximum capacity.', - featured: false, - highlight: false, - badge: null, - }, -]; - -const FEATURED_TIERS = CLOUD_TIERS.filter((t) => t.featured); - -// ─── Resource Unit Costs ────────────────────────────────────────────────────── -// rolling: true = count persists across billing cycles (does not reset) -const RESOURCE_UNITS = [ - { icon: Package, label: 'Order', units: 2, rolling: false }, - { icon: UserRound, label: 'Contact', units: 1, rolling: false }, - { icon: MapPin, label: 'Place', units: 1, rolling: false }, - { icon: Building2, label: 'Vendor', units: 1, rolling: false }, - { icon: Truck, label: 'Vehicle', units: 1, rolling: true }, - { icon: User, label: 'Driver', units: 1, rolling: true }, - { icon: Receipt, label: 'Service Rate', units: 1, rolling: false }, - { icon: Globe, label: 'Service Area', units: 1, rolling: false }, - { icon: Layers, label: 'Zone', units: 1, rolling: false }, - { icon: Users, label: 'User', units: 5, rolling: true }, - { icon: Webhook, label: 'Webhook', units: 5, rolling: true }, - { icon: Key, label: 'API Key', units: 1, rolling: true }, -]; - -// ─── Overage Packs ──────────────────────────────────────────────────────────── -const OVERAGE_PACKS = [ - { name: 'Small', price: 90, units: 100 }, - { name: 'Medium', price: 240, units: 300 }, - { name: 'Large', price: 375, units: 500 }, - { name: 'Jumbo', price: 700, units: 1000 }, -]; +import PricingCalculator from './pricing-calculator'; +import { + CLOUD_TIERS, + FEATURED_TIERS, + OVERAGE_PACKS, + RESOURCE_UNITS, + type CloudTier, +} from './pricing-data'; // ─── Support Tiers ──────────────────────────────────────────────────────────── const SUPPORT_TIERS = [ @@ -346,11 +189,23 @@ const LICENSE_OPTIONS = [ }, ]; -// ─── FAQs ───────────────────────────────────────────────────────────────────── +// ─── FAQs (v3 pricing) ──────────────────────────────────────────────────────── const FAQS = [ + { + q: 'How does Fleetbase compare to Onfleet, Detrack, or Routific?', + a: 'Most per-driver TMS platforms charge $29–$99 per driver per month — so a 10-driver fleet typically pays $290–$990/month just for driver seats, before any orders, integrations, or user logins. Fleetbase doesn’t charge per driver. A 10-driver fleet processing 400 orders/month fits on the Essential plan at $100/month, with the full platform included (Fleet-Ops, Storefront, Pallet, Ledger, Marketplace). Use the calculator above to compare with your numbers.', + }, { q: 'What is a Resource Unit?', - a: 'Resource Units are the currency of your Fleetbase Cloud plan. Each resource type consumes a set number of units: Orders = 2 units; Users = 5 units; Webhooks = 5 units; Contacts, Places, Vendors, Vehicles, Drivers, Service Rates, Service Areas, Zones, and API Keys = 1 unit each. Most resources reset each billing cycle. Rolling resources — Users, Vehicles, Drivers, Webhooks, and API Keys — do not reset; their count persists into the next billing cycle. You only pay overage for usage beyond your monthly allocation.', + a: 'Resource Units are how we measure the structural size of your operation — things like vehicles, customer contacts, delivery addresses, service rates, zones, and users. Each plan includes a monthly allocation. Drivers and orders are tracked for visibility but are NEVER billable — so growing your team or running more deliveries does not increase your bill. Most resources reset each billing cycle. Rolling resources (Users, Vehicles, Webhooks, API Keys) carry their count into the next cycle.', + }, + { + q: 'Are orders and drivers really free?', + a: 'Yes — neither contributes any units toward your monthly allocation, regardless of how many you have or process. You can add as many drivers as you need and run as many orders as you want without your bill changing. We make money on the resource units that represent the structural size of your business (vehicles, contacts, places, users) and on bundled platform value, not on activity or workforce headcount.', + }, + { + q: 'How much does each resource cost in units?', + a: 'Vehicles, contacts, places, vendors, service rates, service areas, zones, and API keys are 1 unit each. Users are 5 units each (rolling — they persist across cycles). Webhook endpoints are 5 units each (rolling). Orders and drivers are 0 — they are tracked but never billable.', }, { q: 'Can I switch plans at any time?', @@ -366,7 +221,7 @@ const FAQS = [ }, { q: 'Is there a free trial?', - a: 'Yes — every Cloud plan includes a 7-day free trial capped at 50 resource units. Billing begins when either limit is reached first, so you can evaluate the platform against real operational usage.', + a: 'Yes — every Cloud plan includes a 7-day free trial capped at 100 resource units. Billing begins when either limit is reached first, so you can evaluate the platform against real operational usage.', }, { q: 'What does the Self-Hosted implementation fee include?', @@ -376,17 +231,13 @@ const FAQS = [ q: 'Can I add more Resource Units mid-month?', a: 'Yes. You can purchase Resource Unit Packs at any time: Small (100 units / $90), Medium (300 units / $240), Large (500 units / $375), or Jumbo (1,000 units / $700). These top up your allocation immediately.', }, - { - q: 'What is Professional Services?', - a: 'Professional Services covers custom development work — building bespoke extensions, integrating with your existing ERP/CRM, custom workflow automation, data migration, and training. Pricing is scoped per project. Contact our sales team for a quote.', - }, ]; export default function PricingClient() { const [billing, setBilling] = useState<'monthly' | 'annual'>('monthly'); const [showAllPlans, setShowAllPlans] = useState(false); - const tierPrice = (tier: (typeof CLOUD_TIERS)[0]) => + const tierPrice = (tier: CloudTier) => billing === 'annual' ? tier.annualPrice : tier.monthlyPrice; useEffect(() => { @@ -401,7 +252,7 @@ export default function PricingClient() { track('pricing_billing_toggled', { to_cycle: cycle }); }; - const onTierCtaClick = (tier: (typeof CLOUD_TIERS)[0]) => { + const onTierCtaClick = (tier: CloudTier) => { track('pricing_tier_cta_clicked', { tier: tier.name, billing_cycle: billing, @@ -416,14 +267,19 @@ export default function PricingClient() {
- Transparent, Usage-Based Pricing + Drivers and orders are free — always

- Pay for what you use.{' '} - Nothing more. + One price for your whole fleet.{' '} + Not per driver.

-

- Fleetbase Cloud starts at $25/month. No per-seat charges. No hidden fees. Self-hosted implementation is a one-time $2,500 fee. +

+ Most TMS platforms charge $29–$99 per driver per month. Fleetbase charges $0. + Add as many drivers and process as many orders as you need — your bill won't + change. You pay for the structure of your business, not its activity or workforce. +

+

+ Cloud starts at $25/month. Self-hosted from $2,500 one-time.

{/* Billing Toggle */} @@ -472,19 +328,17 @@ export default function PricingClient() {

- 7 days or 50 resource units — whichever comes first. + 7 days or 100 resource units — whichever comes first.

@@ -494,8 +348,9 @@ export default function PricingClient() {

Fleetbase Cloud

-

- Fully managed. Automatic updates. Unlimited users and drivers. All platform modules included on every plan. +

+ Fully managed. Automatic updates. Unlimited drivers and orders on every plan. + All platform modules included.

@@ -504,7 +359,10 @@ export default function PricingClient() { {FEATURED_TIERS.map((tier) => ( {tier.badge && (
@@ -531,7 +389,10 @@ export default function PricingClient() {
Overage: ${tier.overage}/unit
-
{tier.description}
+ {tier.fits && ( +
Fits: {tier.fits}
+ )} +
{tier.description}
@@ -640,29 +501,57 @@ export default function PricingClient() {
+ {/* Calculator */} + + {/* Resource Units Explainer */} -
+
-

How Resource Units Work

+

One plan covers everything you do

- Your plan includes a monthly unit allocation. Each resource type consumes a set number of units per item created. Most resources reset each billing cycle — rolling resources (marked below) carry their count into the next cycle. + Per-driver pricing punishes growth. Instead, each plan gives you a monthly + activity allowance measured in resource units. + Drivers and orders are free. Everything else + draws from your monthly budget — so you can grow your team and run more deliveries without + your bill changing.

{RESOURCE_UNITS.map((r) => ( -
- -
{r.units}
+
+ + {r.billable ? ( +
{r.units}
+ ) : ( +
FREE
+ )}
{r.label}
- {r.rolling && ( -
rolling
+ {r.rolling && r.billable && ( +
+ rolling +
)}
))}
-
+
+
+ + Free — never billable +
Resets each billing cycle @@ -685,7 +574,9 @@ export default function PricingClient() {
{pack.name}
${pack.price}
-
{pack.units.toLocaleString()} units
+
+ {pack.units.toLocaleString()} units +
))}
@@ -694,12 +585,13 @@ export default function PricingClient() {
{/* Self-Hosted + Professional Services */} -
+

Other Deployment Options

- Full control over your infrastructure, or custom-built solutions for your unique requirements. + Full control over your infrastructure, or custom-built solutions for your unique + requirements.

@@ -791,19 +683,23 @@ export default function PricingClient() {
{/* Support Tiers */} -
+

Support Levels

- Choose the level of ongoing support that matches your team's capacity and ambition. Available for both Cloud and Self-Hosted customers. + Choose the level of ongoing support that matches your team's capacity and ambition. + Available for both Cloud and Self-Hosted customers.

{SUPPORT_TIERS.map((tier) => (
@@ -841,19 +737,24 @@ export default function PricingClient() {
{/* Commercial License Options */} -
+

Commercial License Options

- Building proprietary extensions or integrations? A Commercial License waives AGPL obligations and keeps your custom code private. Fleetbase Core remains open-source — only your extensions are covered. + Building proprietary extensions or integrations? A Commercial License waives AGPL + obligations and keeps your custom code private. Fleetbase Core remains open-source — + only your extensions are covered.

{LICENSE_OPTIONS.map((lic) => ( {lic.highlight && (
@@ -899,7 +800,10 @@ export default function PricingClient() { Read our licensing guide {' '} or{' '} - + talk to our team . @@ -908,7 +812,7 @@ export default function PricingClient() {
{/* FAQ */} -
+

Frequently Asked Questions

@@ -923,7 +827,9 @@ export default function PricingClient() { {faq.q} - {faq.a} + + {faq.a} + ))} @@ -931,14 +837,15 @@ export default function PricingClient() {
{/* Bottom CTA */} -
+

Ready to get started?

- Try Fleetbase free for 7 days or 50 resource units, whichever comes first. Or speak to our team to find the right plan for your operation. + Try Fleetbase free for 7 days or 100 resource units, whichever comes first. Or speak + to our team to find the right plan for your operation.