2026 03 20 Per Service Pricing
Per-Service Pricing Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the single global /pricing page with dedicated, fine-grained pricing sections embedded directly on each service's marketing page, featuring hourly rates for DevOps/consulting services, monthly managed rates for infrastructure, and an interactive calculator for managed infra.
Architecture: Centralize all pricing data in lib/service-pricing-data.ts, inject a ServicePricingSection component into the service page template (_pages/services/[slug].tsx), and build a ManagedInfraCalculator for database/ops service pages. Remove the global Pricing nav link and per-dropdown Pricing links from data/nav.tsx.
Tech Stack: Next.js 15, React, TypeScript, Tailwind CSS, Lucide React, ui package (Button, cn)
File Map#
Pricing Model Reference#
DevOps & SRE Services (hourly rate + monthly retainer)#
- DevOps as a Service: $120/hr, min 40 hr/month retainer ($4,800/mo)
- SRE as a Service: $130/hr, min 40 hr/month retainer ($5,200/mo)
- DevOps Emergency: $200/hr, min 4 hours, 24/7 response
- DevOps Trainings: $150/hr per trainer (standard = 16h, up to 10 people)
Cloud Account Management (monthly)#
- Starter: $1,500/mo (up to 2 accounts) — keep existing
- Professional: $3,500/mo (up to 10 accounts) — keep existing
- Enterprise: Custom
Managed Kubernetes (monthly)#
- Keep existing tiers ($3,000 / $6,000 / Custom)
Managed Infrastructure — Databases (monthly managed)#
Managed Infrastructure — Ops (monthly)#
Consulting & Advisory (project / hourly)#
Productivity Suites (per-user/mo)#
Task 1: Remove Pricing from Navigation#
Files:
-
Modify:
apps/www/data/nav.tsx -
Step 1: Remove top-level Pricing menu item
In getMenu(), remove:
1{2 title: 'Pricing',3 url: '/pricing',4},- Step 2: Remove Pricing links from devopsGroups
Remove the entry { text: 'Pricing', url: '/docs/services/pricing', ... } from devopsGroups[0].links.
- Step 3: Remove Pricing links from cloudGroups
Remove the Pricing entry from cloudGroups[0].links (the Cloud group).
- Step 4: Remove Pricing links from managedGroups
Remove the Pricing entry from managedGroups[1].links (the Observability & Ops group).
- Step 5: Remove Pricing links from consultingGroups
Remove the Pricing entry from consultingGroups[1].links (the Advisory group).
- Step 6: Update getFooterServicesLinks to remove Pricing
Change the footer helper — remove { text: 'Pricing', url: '/pricing' } from the returned array.
- Step 7: Commit
1git add apps/www/data/nav.tsx2git commit -m "feat: remove global Pricing nav link — pricing now lives on service pages"Task 2: Create Centralized Pricing Data#
Files:
-
Create:
apps/www/lib/service-pricing-data.ts -
Step 1: Create the file with types and data
1// apps/www/lib/service-pricing-data.ts23export type = 'hourly' | 'monthly-tiers' | 'managed-infra' | 'per-user' | 'fixed-project'45export interface {6 : 'hourly'7 : number // $ per hour8 ?: number // minimum engagement hours9 ?: number // pre-calculated retainer if applicable10 : string11}1213export interface {14 : 'monthly-tiers'15 : {16 : string17 : number | 'custom'18 : string19 : string20 ?: boolean21 : { : string; : boolean }[]22 : { : string; : string }23 }[]24}2526export interface {27 : 'per-user'28 : number // $ per user per month29 : number30 : number31 : string // e.g. "users", "mailboxes"32 : string33}3435export interface {36 : 'fixed-project'37 : number38 : string39 : string[]40 : { : string; : string }41}4243export type =44 | 45 | 46 | 47 | 4849export const : <string, > = {50 'devops-as-a-service': {51 : 'hourly',52 : 120,53 : 40,54 : 4800,55 : 'Dedicated DevOps engineers embedded in your team. Billed hourly with a 40-hour monthly minimum.',56 },57 'sre-as-a-service': {58 : 'hourly',59 : 130,60 : 40,61 : 5200,62 : '24/7 reliability engineering. On-call, incident response, and proactive hardening.',63 },64 : {65 : 'hourly',66 : 200,67 : 4,68 : 'Immediate senior engineer response for production incidents. Available 24/7, billed in 4-hour blocks.',69 },70 'devops-trainings': {71 : 'hourly',72 : 150,73 : 'Expert-led hands-on training. Standard curriculum is 16 hours (up to 10 people per session).',74 },75 'cloud-account-management': {76 : 'monthly-tiers',77 : [78 {79 : 'Starter',80 : 1500,81 : '/mo',82 : 'Small teams managing 1-2 cloud accounts with basic governance needs.',83 : [84 { : 'Up to 2 cloud accounts', : true },85 { : 'Monthly cost review', : true },86 { : 'Basic IAM policies', : true },87 { : 'Email support', : true },88 { : 'Multi-cloud support', : false },89 { : 'Dedicated engineer', : false },90 { : 'Compliance reporting', : false },91 ],92 : { : 'Get started', : '/contact' },93 },94 {95 : 'Professional',96 : 3500,97 : '/mo',98 : 'Growing teams with multiple accounts and active cost optimization.',99 : true,100 : [101 { : 'Up to 10 cloud accounts', : true },102 { : 'Weekly cost optimization', : true },103 { : 'Advanced IAM & network segmentation', : true },104 { : 'Slack + email support', : true },105 { : 'Multi-cloud support', : true },106 { : 'Dedicated engineer', : true },107 { : 'Compliance reporting', : false },108 ],109 : { : 'Get started', : '/contact' },110 },111 {112 : 'Enterprise',113 : 'custom',114 : '',115 : 'Organizations with complex multi-cloud estates and compliance requirements.',116 : [117 { : 'Unlimited cloud accounts', : true },118 { : 'Continuous cost optimization', : true },119 { : 'CIS benchmarks & audit-ready reporting', : true },120 { : 'Dedicated Slack + phone support', : true },121 { : 'Multi-cloud support', : true },122 { : 'Dedicated engineer team', : true },123 { : 'SOC2/HIPAA compliance reporting', : true },124 ],125 : { : 'Contact us', : '/contact' },126 },127 ],128 },129 'managed-kubernetes': {130 : 'monthly-tiers',131 : [132 {133 : 'Starter',134 : 3000,135 : '/mo',136 : 'Teams running their first production K8s cluster.',137 : [138 { : '1 production cluster', : true },139 { : 'HA control plane', : true },140 { : 'Basic monitoring & alerting', : true },141 { : 'Email support', : true },142 { : 'GitOps deployment', : false },143 { : 'Multi-cluster management', : false },144 { : 'Custom platform engineering', : false },145 ],146 : { : 'Get started', : '/contact' },147 },148 {149 : 'Professional',150 : 6000,151 : '/mo',152 : 'Production workloads needing full-stack platform engineering.',153 : true,154 : [155 { : 'Up to 3 clusters', : true },156 { : 'HA control plane + autoscaling', : true },157 { : 'Full observability stack', : true },158 { : 'Slack + email support', : true },159 { : 'GitOps deployment', : true },160 { : 'Multi-cluster management', : true },161 { : 'Custom platform engineering', : false },162 ],163 : { : 'Get started', : '/contact' },164 },165 {166 : 'Enterprise',167 : 'custom',168 : '',169 : 'Critical workloads at scale across multiple regions.',170 : [171 { : 'Unlimited clusters', : true },172 { : 'HA + multi-region', : true },173 { : 'Full observability + custom dashboards', : true },174 { : 'Dedicated Slack + phone', : true },175 { : 'GitOps + advanced deployment strategies', : true },176 { : 'Multi-cluster + federation', : true },177 { : 'Custom platform engineering', : true },178 ],179 : { : 'Contact us', : '/contact' },180 },181 ],182 },183 'google-apps-management': {184 : 'per-user',185 : 8,186 : 10,187 : 800,188 : 'users',189 : 'Full Google Workspace administration — user lifecycle, security controls, and day-to-day support. One-time setup fee covers migration and initial configuration.',190 },191 'office365-management': {192 : 'per-user',193 : 9,194 : 10,195 : 900,196 : 'users',197 : 'Complete Microsoft 365 tenant management — security hardening, user provisioning, Teams/SharePoint, and ongoing support. One-time setup fee covers migration and onboarding.',198 },199 'sovereign-productivity-suite': {200 : 'per-user',201 : 6,202 : 25,203 : 600,204 : 'mailboxes',205 : 'Fully self-hosted Zimbra email with OnlyOffice documents and Nextcloud storage. Your data stays on your infrastructure. One-time setup fee covers server provisioning and migration.',206 },207 'security-audit': {208 : 'fixed-project',209 : 4800,210 : 'Comprehensive security posture assessment. Delivered in ~4 days.',211 : [212 'Vulnerability assessment across infrastructure and applications',213 'IAM and access control review',214 'Network segmentation and firewall audit',215 'Prioritized remediation report with executive summary',216 ],217 : { : 'Request audit', : '/contact' },218 },219 'infrastructure-audit': {220 : 'fixed-project',221 : 3200,222 : 'Architecture and reliability review. Delivered in ~3 days.',223 : [224 'Infrastructure architecture review',225 'Cost and performance analysis',226 'Scalability and resilience assessment',227 'Actionable recommendations report',228 ],229 : { : 'Request audit', : '/contact' },230 },231 'cicd-audit': {232 : 'fixed-project',233 : 2400,234 : 'Pipeline efficiency and best-practices review. Delivered in ~2 days.',235 : [236 'Build and deployment pipeline analysis',237 'Test coverage and quality gate review',238 'Security scanning integration check',239 'Optimization and tooling recommendations',240 ],241 : { : 'Request audit', : '/contact' },242 },243 'technology-consulting': {244 : 'hourly',245 : 120,246 : 20,247 : 'Strategic technology guidance from senior engineers. Architecture decisions, technology selection, and roadmap planning.',248 },249 'solution-for-saas': {250 : 'hourly',251 : 120,252 : 20,253 : 'Infrastructure architecture tailored for SaaS products — multi-tenancy, billing integrations, scalability patterns.',254 },255 'solution-for-paas': {256 : 'hourly',257 : 120,258 : 20,259 : 'Platform-as-a-Service architecture design — control planes, tenant isolation, self-service provisioning.',260 },261}262263/** Services that use managed-infra calculator instead of (or in addition to) tiers */264export const = new ([265 'managed-postgresql',266 'managed-mysql',267 'managed-mongodb',268 'managed-redis',269 'managed-kafka',270 'managed-opensearch',271 'managed-runners',272 'managed-prometheus',273 'managed-docker-registry',274 'managed-artifact-repos',275 'managed-dns',276 'managed-certificates',277])- Step 2: Commit
1git add apps/www/lib/service-pricing-data.ts2git commit -m "feat: add centralized per-service pricing data"Task 3: Create Managed Infra Pricing Logic#
Files:
-
Create:
apps/www/lib/managed-infra-pricing.ts -
Step 1: Create the file
1// apps/www/lib/managed-infra-pricing.ts23export type = 'small' | 'medium' | 'large'45export interface {6 : string7 : string8 : 'database' | 'ops'9 : string // e.g. "instance", "runners", "zones"10 ?: {11 : number | null12 : number | null13 : number | null14 ?: string // e.g. "HA Cluster" instead of "Large"15 }16 // For per-unit services (runners, DNS zones, certs)17 ?: {18 : number19 : string // e.g. "per runner/mo"20 : number21 : number22 : number[] // breakpoint quantities23 : number[] // price at each breakpoint24 }25 ?: number // % uplift for HA, or null if HA is included in large26}2728export const : [] = [29 {30 : 'managed-postgresql',31 : 'PostgreSQL',32 : 'database',33 : 'instance',34 : { : 400, : 800, : 1600, : 'Large (HA)' },35 },36 {37 : 'managed-mysql',38 : 'MySQL',39 : 'database',40 : 'instance',41 : { : 350, : 700, : 1400, : 'Large (HA)' },42 },43 {44 : 'managed-mongodb',45 : 'MongoDB',46 : 'database',47 : 'replica set',48 : { : 600, : 1200, : 2400, : 'Sharded Cluster' },49 },50 {51 : 'managed-redis',52 : 'Redis',53 : 'database',54 : 'instance',55 : { : 200, : 350, : 600, : 'Cluster Mode' },56 },57 {58 : 'managed-kafka',59 : 'Kafka',60 : 'database',61 : 'cluster',62 : { : 800, : 1600, : null, : 'Enterprise' },63 },64 {65 : 'managed-opensearch',66 : 'OpenSearch',67 : 'database',68 : 'cluster',69 : { : 500, : 1000, : 2000, : 'Multi-node' },70 },71 {72 : 'managed-runners',73 : 'Self-Hosted Runners',74 : 'ops',75 : 'runners',76 : {77 : 50,78 : 'per runner/mo',79 : 5,80 : 250,81 : [5, 15, 30, 50],82 : [250, 650, 1200, 1900],83 },84 },85 {86 : 'managed-prometheus',87 : 'Prometheus',88 : 'ops',89 : 'setup',90 : { : 400, : null, : 700, : 'HA Setup' },91 },92 {93 : 'managed-docker-registry',94 : 'Artifactory',95 : 'ops',96 : 'repository service',97 : { : 300, : null, : null },98 },99 {100 : 'managed-artifact-repos',101 : 'Artifact Repos',102 : 'ops',103 : 'repository',104 : { : 400, : null, : null },105 },106 {107 : 'managed-dns',108 : 'DNS as a Service',109 : 'ops',110 : 'zones',111 : {112 : 20,113 : 'per zone/mo',114 : 1,115 : 200,116 : [10, 50],117 : [200, 400],118 },119 },120 {121 : 'managed-certificates',122 : 'Certificate Management',123 : 'ops',124 : 'certificates',125 : {126 : 8,127 : 'per cert/mo',128 : 1,129 : 150,130 : [20, 100],131 : [150, 300],132 },133 },134]135136export function (: string): | undefined {137 return .(() => . === )138}139140export function (: , : , ?: number): number | null {141 if (.) {142 const = .[]143 return // null means "Custom"144 }145 if (. && !== ) {146 const { , , } = .147 for (let = . - 1; >= 0; --) {148 if ( >= []) return []149 }150 return 151 }152 return null153}154155export function (: number | null): string {156 if ( === null) return 'Custom'157 return new .('en-US', {158 : 'currency',159 : 'USD',160 : 0,161 : 0,162 }).()163}- Step 2: Commit
1git add apps/www/lib/managed-infra-pricing.ts2git commit -m "feat: add managed infra pricing logic"Task 4: Create ServicePricingSection Component#
Files:
- Create:
apps/www/components/Pricing/ServicePricingSection.tsx
This component reads from service-pricing-data.ts and renders the correct pricing UI based on the model type.
- Step 1: Create the component
1// apps/www/components/Pricing/ServicePricingSection.tsx2'use client'34import Link from 'next/link'5import { Check, Minus, Clock, Users, FolderCheck } from 'lucide-react'6import { Button, cn } from 'ui'7import { servicePricing } from '~/lib/service-pricing-data'8import type {9 HourlyPricing,10 MonthlyTierPricing,11 PerUserPricing,12 FixedProjectPricing,13} from '~/lib/service-pricing-data'1415// ── Hourly ───────────────────────────────────────────────────────────────────16function HourlySection({ pricing }: { pricing: HourlyPricing }) {17 const retainer = pricing.monthlyRetainer ?? (pricing.rate * (pricing.minHours ?? 1))18 return (19 <div className="rounded-xl border border-default bg-surface-75 p-8 max-w-2xl">20 <div className="flex items-center gap-2 text-foreground-lighter text-sm mb-4">21 <Clock className="w-4 h-4" />22 <span>Hourly rate</span>23 </div>24 <div className="flex items-baseline gap-2 mb-2">25 <span className="text-5xl font-medium font-mono text-foreground">${pricing.rate}</span>26 <span className="text-foreground-lighter">/hr</span>27 </div>28 {pricing.minHours && (29 <p className="text-sm text-foreground-lighter mb-1">30 Minimum engagement: {pricing.minHours} hours31 {pricing.monthlyRetainer ? ` (${new Intl.NumberFormat('en-US',{style:'currency',currency:'USD',minimumFractionDigits:0}).format(retainer)}/mo retainer)` : ''}32 </p>33 )}34 <p className="text-foreground-lighter text-sm mt-3 mb-6">{pricing.description}</p>35 <div className="flex gap-3">36 <Button asChild size="medium" type="primary">37 <Link href="/contact">Get a quote</Link>38 </Button>39 <Button asChild size="medium" type="default">40 <Link href="/contact">Talk to us</Link>41 </Button>42 </div>43 </div>44 )45}4647// ── Monthly Tiers ─────────────────────────────────────────────────────────────48function MonthlyTiersSection({ pricing }: { pricing: MonthlyTierPricing }) {49 return (50 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 lg:gap-6">51 {pricing.tiers.map((tier) => {52 const priceDisplay = tier.price === 'custom' ? 'Custom' : `$${tier.price.toLocaleString()}`53 return (54 <div55 key={tier.name}56 className={cn(57 'flex flex-col border rounded-xl bg-surface-75 p-6 lg:p-8 relative',58 tier.highlight && 'border-brand ring-1 ring-brand'59 )}60 >61 {tier.highlight && (62 <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-brand text-background text-xs font-medium px-3 py-1 rounded-full">63 Most popular64 </div>65 )}66 <h4 className="text-foreground text-lg font-medium font-mono uppercase">{tier.name}</h4>67 <div className="mt-3 flex items-baseline gap-1">68 <span className="text-foreground text-4xl font-medium font-mono">{priceDisplay}</span>69 {tier.priceUnit && (70 <span className="text-foreground-lighter text-sm">{tier.priceUnit}</span>71 )}72 </div>73 <p className="text-foreground-lighter text-sm mt-3 min-h-[3rem]">{tier.description}</p>74 <Button asChild size="medium" type={tier.highlight ? 'primary' : 'default'} block className="mt-6">75 <Link href={tier.cta.href}>{tier.cta.label}</Link>76 </Button>77 <ul className="mt-6 space-y-3 flex-1">78 {tier.features.map((feature) => (79 <li key={feature.label} className="flex items-start gap-2.5 text-sm">80 {feature.included ? (81 <Check className="w-4 h-4 text-brand shrink-0 mt-0.5" strokeWidth={2.5} />82 ) : (83 <Minus className="w-4 h-4 text-foreground-muted shrink-0 mt-0.5" strokeWidth={2} />84 )}85 <span className={cn(feature.included ? 'text-foreground' : 'text-foreground-muted')}>86 {feature.label}87 </span>88 </li>89 ))}90 </ul>91 </div>92 )93 })}94 </div>95 )96}9798// ── Per User ──────────────────────────────────────────────────────────────────99function PerUserSection({ pricing }: { pricing: PerUserPricing }) {100 const minMonthly = pricing.ratePerUser * pricing.minUsers101 const fmt = (n: number) =>102 new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0 }).format(n)103 return (104 <div className="rounded-xl border border-default bg-surface-75 p-8 max-w-2xl">105 <div className="flex items-center gap-2 text-foreground-lighter text-sm mb-4">106 <Users className="w-4 h-4" />107 <span>Per {pricing.userLabel}</span>108 </div>109 <div className="flex items-baseline gap-2 mb-2">110 <span className="text-5xl font-medium font-mono text-foreground">${pricing.ratePerUser}</span>111 <span className="text-foreground-lighter">/{pricing.userLabel.replace(/s$/, '')}/mo</span>112 </div>113 <p className="text-sm text-foreground-lighter mb-1">114 Minimum {pricing.minUsers} {pricing.userLabel} — from {fmt(minMonthly)}/mo115 </p>116 <p className="text-sm text-foreground-lighter mb-1">117 One-time setup fee: {fmt(pricing.setupFee)}118 </p>119 <p className="text-foreground-lighter text-sm mt-3 mb-6">{pricing.description}</p>120 <div className="flex gap-3">121 <Button asChild size="medium" type="primary">122 <Link href="/contact">Get started</Link>123 </Button>124 <Button asChild size="medium" type="default">125 <Link href="/contact">Request a quote</Link>126 </Button>127 </div>128 </div>129 )130}131132// ── Fixed Project ─────────────────────────────────────────────────────────────133function FixedProjectSection({ pricing }: { pricing: FixedProjectPricing }) {134 const fmt = (n: number) =>135 new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0 }).format(n)136 return (137 <div className="rounded-xl border border-default bg-surface-75 p-8 max-w-2xl">138 <div className="flex items-center gap-2 text-foreground-lighter text-sm mb-4">139 <FolderCheck className="w-4 h-4" />140 <span>Fixed project price</span>141 </div>142 <div className="flex items-baseline gap-2 mb-3">143 <span className="text-5xl font-medium font-mono text-foreground">{fmt(pricing.price)}</span>144 </div>145 <p className="text-foreground-lighter text-sm mb-4">{pricing.description}</p>146 <ul className="space-y-2 mb-6">147 {pricing.includes.map((item) => (148 <li key={item} className="flex items-start gap-2.5 text-sm">149 <Check className="w-4 h-4 text-brand shrink-0 mt-0.5" strokeWidth={2.5} />150 <span className="text-foreground">{item}</span>151 </li>152 ))}153 </ul>154 <Button asChild size="medium" type="primary">155 <Link href={pricing.cta.href}>{pricing.cta.label}</Link>156 </Button>157 </div>158 )159}160161// ── Main export ───────────────────────────────────────────────────────────────162export default function ServicePricingSection({ slug }: { slug: string }) {163 const pricing = servicePricing[slug]164 if (!pricing) return null165166 return (167 <section className="space-y-6">168 <div>169 <h2 className="text-foreground text-2xl sm:text-3xl font-medium tracking-tight mb-2">Pricing</h2>170 <p className="text-foreground-lighter text-sm">171 No long-term contracts.{' '}172 <Link href="/contact" className="text-brand hover:underline">173 Contact us174 </Link>{' '}175 for custom arrangements.176 </p>177 </div>178 {pricing.model === 'hourly' && <HourlySection pricing={pricing} />}179 {pricing.model === 'monthly-tiers' && <MonthlyTiersSection pricing={pricing} />}180 {pricing.model === 'per-user' && <PerUserSection pricing={pricing} />}181 {pricing.model === 'fixed-project' && <FixedProjectSection pricing={pricing} />}182 </section>183 )184}- Step 2: Commit
1git add apps/www/components/Pricing/ServicePricingSection.tsx2git commit -m "feat: add ServicePricingSection component for service pages"Task 5: Create Managed Infra Calculator Component#
Files:
-
Create:
apps/www/components/Pricing/ManagedInfraCalculator.tsx -
Step 1: Create the component
1// apps/www/components/Pricing/ManagedInfraCalculator.tsx2'use client'34import { useState } from 'react'5import { Button, cn } from 'ui'6import Link from 'next/link'7import {8 managedInfraServices,9 getServiceById,10 estimateMonthlyPrice,11 formatPrice,12 type InfraSize,13 type ManagedInfraService,14} from '~/lib/managed-infra-pricing'1516interface ServiceRow {17 id: string18 enabled: boolean19 size: InfraSize20 units: number21}2223function createDefaultRows(): ServiceRow[] {24 return managedInfraServices.map((s) => ({25 id: s.id,26 enabled: false,27 size: 'small',28 units: s.perUnit?.minUnits ?? 1,29 }))30}3132function calcServicePrice(service: ManagedInfraService, row: ServiceRow): number | null {33 if (service.sizes) {34 return estimateMonthlyPrice(service, row.size)35 }36 if (service.perUnit) {37 return estimateMonthlyPrice(service, row.size, row.units)38 }39 return null40}4142export default function ManagedInfraCalculator({ highlightSlug }: { highlightSlug?: string }) {43 const [rows, setRows] = useState<ServiceRow[]>(createDefaultRows)4445 const total = rows.reduce((sum, row) => {46 if (!row.enabled) return sum47 const svc = getServiceById(row.id)48 if (!svc) return sum49 return sum + (calcServicePrice(svc, row) ?? 0)50 }, 0)5152 const toggleRow = (id: string) =>53 setRows((prev) => prev.map((r) => (r.id === id ? { ...r, enabled: !r.enabled } : r)))5455 const setSize = (id: string, size: InfraSize) =>56 setRows((prev) => prev.map((r) => (r.id === id ? { ...r, size } : r)))5758 const setUnits = (id: string, units: number) =>59 setRows((prev) => prev.map((r) => (r.id === id ? { ...r, units } : r)))6061 const databases = managedInfraServices.filter((s) => s.category === 'database')62 const ops = managedInfraServices.filter((s) => s.category === 'ops')6364 const renderCategory = (services: ManagedInfraService[], label: string) => (65 <div>66 <h3 className="text-foreground-lighter text-xs font-medium uppercase tracking-widest mb-3">{label}</h3>67 <div className="space-y-2">68 {services.map((svc) => {69 const row = rows.find((r) => r.id === svc.id)!70 const price = row.enabled ? calcServicePrice(svc, row) : null71 const isHighlighted = svc.id === highlightSlug72 return (73 <div74 key={svc.id}75 className={cn(76 'flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border p-4 transition-colors',77 row.enabled ? 'border-brand bg-brand/5' : 'border-default bg-surface-75',78 isHighlighted && !row.enabled && 'border-brand/40'79 )}80 >81 {/* Toggle + label */}82 <label className="flex items-center gap-3 cursor-pointer flex-1 min-w-0">83 <input84 type="checkbox"85 checked={row.enabled}86 onChange={() => toggleRow(svc.id)}87 className="accent-brand w-4 h-4 shrink-0"88 />89 <span className="text-foreground text-sm font-medium truncate">{svc.label}</span>90 </label>9192 {/* Size selector (for services with sizes) */}93 {svc.sizes && row.enabled && (94 <div className="flex gap-1 shrink-0">95 {(['small', 'medium', 'large'] as InfraSize[]).map((s) => {96 const p = svc.sizes![s]97 if (p === undefined) return null98 const name = s === 'large' ? (svc.sizes!.largeName ?? 'Large') : s.charAt(0).toUpperCase() + s.slice(1)99 return (100 <button101 key={s}102 onClick={() => setSize(svc.id, s)}103 disabled={p === null}104 className={cn(105 'px-2.5 py-1 text-xs rounded border font-medium transition-colors',106 row.size === s107 ? 'border-brand bg-brand text-background'108 : 'border-default text-foreground-lighter hover:border-foreground-light',109 p === null && 'opacity-40 cursor-not-allowed'110 )}111 >112 {name}113 </button>114 )115 })}116 </div>117 )}118119 {/* Units selector (for per-unit services) */}120 {svc.perUnit && row.enabled && (121 <div className="flex items-center gap-2 shrink-0">122 <input123 type="number"124 min={svc.perUnit.minUnits}125 step={1}126 value={row.units}127 onChange={(e) => setUnits(svc.id, Math.max(svc.perUnit!.minUnits, Number(e.target.value)))}128 className="w-20 px-2 py-1 text-sm border border-default rounded bg-background text-foreground"129 />130 <span className="text-xs text-foreground-lighter whitespace-nowrap">{svc.perUnit.label}</span>131 </div>132 )}133134 {/* Price display */}135 <div className="text-right shrink-0 w-24">136 {row.enabled ? (137 <span className="text-foreground font-medium font-mono text-sm">138 {price === null ? 'Custom' : formatPrice(price)}<span className="text-foreground-lighter font-normal">/mo</span>139 </span>140 ) : (141 <span className="text-foreground-muted text-xs">142 {svc.sizes?.small ? `from ${formatPrice(svc.sizes.small)}/mo` : svc.perUnit ? `from ${formatPrice(svc.perUnit.minPrice)}/mo` : ''}143 </span>144 )}145 </div>146 </div>147 )148 })}149 </div>150 </div>151 )152153 return (154 <div className="space-y-6">155 <div>156 <h2 className="text-foreground text-2xl sm:text-3xl font-medium tracking-tight mb-2">157 Pricing calculator158 </h2>159 <p className="text-foreground-lighter text-sm">160 Select the services you need to estimate your monthly cost.161 </p>162 </div>163164 <div className="space-y-6">165 {renderCategory(databases, 'Databases')}166 {renderCategory(ops, 'Observability & Ops')}167 </div>168169 {/* Total */}170 <div className="rounded-xl border border-default bg-alternative p-6 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">171 <div>172 <p className="text-foreground-lighter text-sm">Estimated monthly total</p>173 <p className="text-foreground text-3xl font-medium font-mono mt-1">174 {formatPrice(total)}175 <span className="text-foreground-lighter text-base font-normal">/mo</span>176 </p>177 <p className="text-foreground-muted text-xs mt-1">178 Does not include server infrastructure costs (compute, storage, egress).179 </p>180 </div>181 <div className="flex gap-3 shrink-0">182 <Button asChild size="medium" type="primary">183 <Link href="/contact">Get an exact quote</Link>184 </Button>185 </div>186 </div>187 </div>188 )189}- Step 2: Commit
1git add apps/www/components/Pricing/ManagedInfraCalculator.tsx2git commit -m "feat: add ManagedInfraCalculator component"Task 6: Inject Pricing into Service Page Template#
Files:
-
Modify:
apps/www/_pages/services/[slug].tsx -
Step 1: Read the current file (already done during planning)
-
Step 2: Add imports
Add at top of _pages/services/[slug].tsx:
1import dynamic from 'next/dynamic'2import { MANAGED_INFRA_SLUGS } from '~/lib/service-pricing-data'34const ServicePricingSection = dynamic(() => import('~/components/Pricing/ServicePricingSection'))5const ManagedInfraCalculator = dynamic(() => import('~/components/Pricing/ManagedInfraCalculator'))- Step 3: Inject pricing section after ComparisonTable
In the JSX render, after the <ComparisonTable> section and before <ServiceTestimonials>, add (with id="pricing" anchor for deep-linking from the hub page):
1{/* Pricing section — rendered for services with pricing data */}2<div id="pricing">3 {!MANAGED_INFRA_SLUGS.has(slug) && (4 <SectionContainer>5 <ServicePricingSection slug={slug} />6 </SectionContainer>7 )}89 {/* Managed infra calculator — rendered for all managed infra services */}10 {MANAGED_INFRA_SLUGS.has(slug) && (11 <SectionContainer>12 <ManagedInfraCalculator highlightSlug={slug} />13 </SectionContainer>14 )}15</div>- Step 4: Commit
1git add apps/www/_pages/services/[slug].tsx2git commit -m "feat: inject pricing section into service page template"Task 7: Convert Global Pricing Page to Hub#
Files:
- Modify:
apps/www/app/pricing/PricingContent.tsx
The /pricing page becomes a lightweight hub that lists services with their pricing model and links to each service's page for full details.
- Step 1: Rewrite PricingContent.tsx
Replace the entire content with:
1'use client'23import Link from 'next/link'4import { Clock, LayoutGrid, Users, FolderCheck, Calculator } from 'lucide-react'5import { Button } from 'ui'6import DefaultLayout from '~/components/Layouts/Default'7import SectionContainer from '~/components/Layouts/SectionContainer'89const sections = [10 {11 category: 'DevOps & SRE',12 icon: Clock,13 description: 'Billed hourly with monthly retainer options.',14 services: [15 { name: 'DevOps as a Service', slug: 'devops-as-a-service', rate: '$120/hr', min: '40 hr/mo minimum' },16 { name: 'SRE as a Service', slug: 'sre-as-a-service', rate: '$130/hr', min: '40 hr/mo minimum' },17 { name: 'DevOps Emergency', slug: 'emergency', rate: '$200/hr', min: '4 hr minimum, 24/7' },18 { name: 'DevOps Trainings', slug: 'devops-trainings', rate: '$150/hr', min: 'per trainer session' },19 ],20 },21 {22 category: 'Cloud & Kubernetes',23 icon: LayoutGrid,24 description: 'Monthly managed service tiers.',25 services: [26 { name: 'Cloud Account Management', slug: 'cloud-account-management', rate: 'from $1,500/mo', min: '' },27 { name: 'Managed Kubernetes', slug: 'managed-kubernetes', rate: 'from $3,000/mo', min: '' },28 { name: 'Managed Kubernetes (K3s option)', slug: 'managed-kubernetes', rate: 'from $3,000/mo', min: '' },29 ],30 },31 {32 category: 'Managed Infrastructure',33 icon: Calculator,34 description: 'Per-service monthly pricing. Use the calculator on each service page.',35 services: [36 { name: 'Managed PostgreSQL', slug: 'managed-postgresql', rate: 'from $400/mo', min: '' },37 { name: 'Managed MySQL', slug: 'managed-mysql', rate: 'from $350/mo', min: '' },38 { name: 'Managed MongoDB', slug: 'managed-mongodb', rate: 'from $600/mo', min: '' },39 { name: 'Managed Redis', slug: 'managed-redis', rate: 'from $200/mo', min: '' },40 { name: 'Managed Kafka', slug: 'managed-kafka', rate: 'from $800/mo', min: '' },41 { name: 'Managed OpenSearch', slug: 'managed-opensearch', rate: 'from $500/mo', min: '' },42 { name: 'Managed Runners', slug: 'managed-runners', rate: 'from $250/mo', min: '5 runners minimum' },43 { name: 'Managed Prometheus', slug: 'managed-prometheus', rate: 'from $400/mo', min: '' },44 { name: 'Managed Artifactory', slug: 'managed-docker-registry', rate: '$300/mo', min: '' },45 { name: 'Managed Artifact Repos', slug: 'managed-artifact-repos', rate: '$400/mo', min: '' },46 { name: 'DNS as a Service', slug: 'managed-dns', rate: 'from $200/mo', min: '' },47 { name: 'Certificate Management', slug: 'managed-certificates', rate: 'from $150/mo', min: '' },48 ],49 },50 {51 category: 'Productivity Suites',52 icon: Users,53 description: 'Per-user/mailbox monthly pricing with one-time setup.',54 services: [55 { name: 'Google Workspace Management', slug: 'google-apps-management', rate: '$8/user/mo', min: '10 users minimum' },56 { name: 'Microsoft 365 Management', slug: 'office365-management', rate: '$9/user/mo', min: '10 users minimum' },57 { name: 'Zimbra (Self-Hosted)', slug: 'sovereign-productivity-suite', rate: '$6/mailbox/mo', min: '25 mailboxes minimum' },58 ],59 },60 {61 category: 'Consulting & Audits',62 icon: FolderCheck,63 description: 'Fixed-price project engagements and hourly advisory.',64 services: [65 { name: 'Security Audit', slug: 'security-audit', rate: '$4,800', min: 'fixed project' },66 { name: 'Infrastructure Audit', slug: 'infrastructure-audit', rate: '$3,200', min: 'fixed project' },67 { name: 'CI/CD Audit', slug: 'cicd-audit', rate: '$2,400', min: 'fixed project' },68 { name: 'Technology Consulting', slug: 'technology-consulting', rate: '$120/hr', min: '20 hr minimum' },69 { name: 'Solution for SaaS', slug: 'solution-for-saas', rate: '$120/hr', min: '20 hr minimum' },70 { name: 'Building PaaS', slug: 'solution-for-paas', rate: '$120/hr', min: '20 hr minimum' },71 ],72 },73]7475export default function PricingContent() {76 return (77 <DefaultLayout>78 <div className="relative z-10 pt-8 pb-4 xl:py-16">79 <div className="mx-auto max-w-7xl px-8 text-center sm:px-6 lg:px-8">80 <h1 className="text-foreground text-3xl sm:text-4xl md:text-5xl font-medium tracking-tight">81 Transparent pricing, no surprises82 </h1>83 <p className="text-foreground-lighter text-lg leading-relaxed max-w-2xl mx-auto mt-4">84 Fixed hourly rates for DevOps work. Monthly managed pricing for infrastructure.85 No long-term contracts required.86 </p>87 <div className="flex flex-wrap items-center justify-center gap-3 pt-4">88 <Button asChild size="medium" type="primary">89 <Link href="/contact">Get a custom quote</Link>90 </Button>91 <Button asChild size="medium" type="default">92 <Link href="/contact">Talk to our team</Link>93 </Button>94 </div>95 </div>96 </div>9798 <SectionContainer>99 <div className="space-y-16">100 {sections.map((section) => {101 const Icon = section.icon102 return (103 <div key={section.category}>104 <div className="flex items-center gap-3 mb-2">105 <Icon className="w-5 h-5 text-brand" />106 <h2 className="text-foreground text-xl font-medium">{section.category}</h2>107 </div>108 <p className="text-foreground-lighter text-sm mb-6">{section.description}</p>109 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">110 {section.services.map((svc) => (111 <Link112 key={svc.slug}113 href={`/services/${svc.slug}#pricing`}114 className="group flex flex-col border border-default rounded-lg p-4 bg-surface-75 hover:border-brand transition-colors"115 >116 <span className="text-foreground text-sm font-medium group-hover:text-brand transition-colors">117 {svc.name}118 </span>119 <span className="text-brand font-mono text-sm mt-1">{svc.rate}</span>120 {svc.min && (121 <span className="text-foreground-muted text-xs mt-0.5">{svc.min}</span>122 )}123 </Link>124 ))}125 </div>126 </div>127 )128 })}129 </div>130 </SectionContainer>131132 <div className="border-t py-16 text-center">133 <div className="max-w-lg mx-auto px-6">134 <h2 className="text-foreground text-2xl font-medium mb-3">Need multiple services?</h2>135 <p className="text-foreground-lighter text-sm mb-6">136 Most clients use 2-4 services together. Contact us for bundled pricing.137 </p>138 <div className="flex flex-wrap items-center justify-center gap-3">139 <Button asChild size="medium" type="primary">140 <Link href="/contact">Get a bundled quote</Link>141 </Button>142 </div>143 </div>144 </div>145 </DefaultLayout>146 )147}- Step 2: Commit
1git add apps/www/app/pricing/PricingContent.tsx apps/www/_pages/services/[slug].tsx2git commit -m "feat: convert global pricing to hub page, add pricing anchors on service pages"Task 8: Update Pricing Page Metadata#
Files:
-
Modify:
apps/www/app/pricing/page.tsx -
Step 1: Update metadata description
Change the description in the page metadata to:
1"Fixed hourly rates for DevOps services. Monthly managed pricing for databases, Kubernetes, and cloud. Transparent pricing on every service page."- Step 2: Commit
1git add apps/www/app/pricing/page.tsx2git commit -m "chore: update pricing page metadata"Verification#
- Run
cd apps/www && pnpm dev— app starts without errors - Visit
/pricing— hub page shows all service categories with rates - Visit
/services/devops-as-a-service#pricing— hourly pricing section renders - Visit
/services/cloud-account-management#pricing— 3-tier monthly pricing renders - Visit
/services/managed-postgresql#pricing— infra calculator renders with PostgreSQL highlighted - Visit
/services/managed-runners#pricing— calculator shows runners with unit selector - Visit
/services/google-apps-management#pricing— per-user pricing renders - Visit
/services/sovereign-productivity-suite#pricing— Zimbra per-mailbox pricing renders - Visit
/services/security-audit#pricing— fixed project pricing renders - Main nav has no "Pricing" top-level link
- No dropdown menus show a "Pricing" entry
- Footer services links do not include "Pricing"
- Calculator total updates reactively when toggling services