Menu

Change language

GitHub repo

Pricing Usage Calculator Slider

blockmarketing

Interactive frontend infrastructure ROI calculator with real-time MAU, QPS, and edge replica sliders and annual cost savings scorecard.

Installation

$npx uipkge-ng@latest add pricing-usage-calculator-slider
Manifest:https://uipkge.dev/r/angular/pricing-usage-calculator-slider.jsonInstalls to:src/app/components/blocks/pricing-usage-calculator-slider/

Variants

Loading interactive previews…

npm dependencies

Files installed (2)

  • src/app/components/blocks/pricing-usage-calculator-slider/pricing-usage-calculator-slider.component.ts10.1 kB
    // Infrastructure ROI calculator: MAU / QPS / edge-replica sliders against a
    // traditional monolith cost model. Port of the Vue PricingUsageCalculatorSlider 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { ArrowRight, Calculator, Globe, LucideAngularModule, Server, Users } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent } from '@/ui/card/card.component'
    
    export function formatCompactNumber(num: number): string {
      if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'
      if (num >= 1000) return (num / 1000).toFixed(0) + 'k'
      return num.toString()
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-pricing-usage-calculator-slider, [ui-pricing-usage-calculator-slider]',
      standalone: true,
      host: { '[attr.class]': '"contents"' },
      imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent],
      template: `
        <section data-slot="pricing-usage-calculator-slider" [class]="rootClass">
          <div class="mx-auto max-w-6xl space-y-12">
            <div class="mx-auto max-w-3xl space-y-4 text-center">
              <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs">
                <lucide-icon [img]="Calculator" class="text-primary size-3.5" />
                Interactive Infrastructure ROI Calculator
              </span>
              <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">
                Calculate your annual savings with unbundled architecture.
              </h2>
              <p class="text-muted-foreground text-base">
                See how eliminating proprietary seat licenses and runtime SaaS wrappers cuts your front-end TCO.
              </p>
    
              <div class="flex flex-wrap items-center justify-center gap-2 pt-2">
                <button ui-button size="sm" variant="outline" class="font-mono text-xs" (click)="applyPreset('seed')">
                  Seed Startup (25k MAU)
                </button>
                <button ui-button size="sm" variant="outline" class="font-mono text-xs" (click)="applyPreset('growth')">
                  Growth Scale (350k MAU)
                </button>
                <button ui-button size="sm" variant="outline" class="font-mono text-xs" (click)="applyPreset('scale')">
                  Hypergrowth (2.5M MAU)
                </button>
              </div>
            </div>
    
            <div class="grid grid-cols-1 items-stretch gap-8 lg:grid-cols-12">
              <div
                ui-card
                class="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl sm:p-8 lg:col-span-7"
              >
                <div class="space-y-6">
                  <div class="border-border flex items-center justify-between border-b pb-4">
                    <h3 class="text-foreground font-mono text-sm font-bold">Traffic &amp; Telemetry Inputs</h3>
                    <span class="text-muted-foreground font-mono text-xs">Dynamic Projection</span>
                  </div>
    
                  <div class="space-y-2">
                    <div class="flex items-center justify-between font-mono text-xs">
                      <span class="text-muted-foreground flex items-center gap-1.5">
                        <lucide-icon [img]="Users" class="text-primary size-3.5" /> Monthly Active Users (MAU)
                      </span>
                      <span class="text-foreground text-sm font-bold">{{ formatNumber(mau()) }} users</span>
                    </div>
                    <input
                      type="range"
                      min="10000"
                      max="5000000"
                      step="25000"
                      [value]="mau()"
                      (input)="mau.set($any($event.target).valueAsNumber)"
                      class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg"
                      aria-label="Monthly active users"
                    />
                  </div>
    
                  <div class="space-y-2">
                    <div class="flex items-center justify-between font-mono text-xs">
                      <span class="text-muted-foreground flex items-center gap-1.5">
                        <lucide-icon [img]="Server" class="text-primary size-3.5" /> Peak Query Throughput
                      </span>
                      <span class="text-foreground text-sm font-bold">{{ formatNumber(qps()) }} QPS</span>
                    </div>
                    <input
                      type="range"
                      min="100"
                      max="20000"
                      step="100"
                      [value]="qps()"
                      (input)="qps.set($any($event.target).valueAsNumber)"
                      class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg"
                      aria-label="Peak query throughput"
                    />
                  </div>
    
                  <div class="space-y-2">
                    <div class="flex items-center justify-between font-mono text-xs">
                      <span class="text-muted-foreground flex items-center gap-1.5">
                        <lucide-icon [img]="Globe" class="text-primary size-3.5" /> Global Edge POP Replicas
                      </span>
                      <span class="text-foreground text-sm font-bold">{{ edgeReplicas() }} Edge Regions</span>
                    </div>
                    <input
                      type="range"
                      min="1"
                      max="12"
                      step="1"
                      [value]="edgeReplicas()"
                      (input)="edgeReplicas.set($any($event.target).valueAsNumber)"
                      class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg"
                      aria-label="Global edge replicas"
                    />
                  </div>
                </div>
    
                <div
                  class="border-border text-muted-foreground flex items-center justify-between border-t pt-4 font-mono text-xs"
                >
                  <span>Formula: Direct AST + Raw Static Hosting</span>
                  <span class="text-success font-semibold">✓ Zero Seat Surcharges</span>
                </div>
              </div>
    
              <div
                ui-card
                class="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-sm sm:p-8 lg:col-span-5"
              >
                <div class="space-y-6">
                  <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success font-mono text-xs">
                    Projected Annual Net Savings
                  </span>
    
                  <div class="space-y-1">
                    <div class="text-success font-mono text-4xl font-bold tracking-tight sm:text-5xl">
                      \${{ annualSavings().toLocaleString() }}
                    </div>
                    <p class="text-muted-foreground font-mono text-xs">
                      Saved every year (\${{ monthlySavings().toLocaleString() }}/month)
                    </p>
                  </div>
    
                  <div class="space-y-3 pt-2">
                    <div class="space-y-1">
                      <div class="flex items-center justify-between font-mono text-xs">
                        <span class="text-destructive font-medium">Traditional Monolith Stack:</span>
                        <span class="text-foreground font-bold">\${{ traditionalCost().toLocaleString() }}/mo</span>
                      </div>
                      <div class="bg-destructive/20 h-2 overflow-hidden rounded-full">
                        <div class="bg-destructive h-full w-full"></div>
                      </div>
                    </div>
    
                    <div class="space-y-1">
                      <div class="flex items-center justify-between font-mono text-xs">
                        <span class="text-success font-semibold">UIPKGE Unbundled Registry:</span>
                        <span class="text-foreground font-bold">\${{ uipkgeCost().toLocaleString() }}/mo</span>
                      </div>
                      <div class="bg-success/20 h-2 overflow-hidden rounded-full">
                        <div
                          class="bg-success h-full transition-[width] duration-300"
                          [style.width]="uipkgeBarWidth() + '%'"
                        ></div>
                      </div>
                    </div>
                  </div>
                </div>
    
                <button ui-button class="mt-4 h-10 w-full gap-1.5 font-mono text-xs shadow-md">
                  <span>Lock In Pro Savings</span>
                  <lucide-icon [img]="ArrowRight" class="size-3.5" />
                </button>
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiPricingUsageCalculatorSliderComponent {
      protected readonly ArrowRight = ArrowRight
      protected readonly Calculator = Calculator
      protected readonly Globe = Globe
      protected readonly Server = Server
      protected readonly Users = Users
    
      @Input('class') className?: string
    
      readonly mau = signal(100000)
      readonly qps = signal(500)
      readonly edgeReplicas = signal(3)
    
      readonly traditionalCost = computed(() => {
        const baseSeatFee = 350
        const perUserFee = (this.mau() / 1000) * 1.8
        const qpsSurcharge = this.qps() * 0.25
        return Math.round(baseSeatFee + perUserFee + qpsSurcharge)
      })
    
      readonly uipkgeCost = computed(() => {
        const rawBandwidth = (this.mau() / 100000) * 4
        const flatProLicense = 24
        return Math.round(flatProLicense + rawBandwidth)
      })
    
      readonly monthlySavings = computed(() => Math.max(0, this.traditionalCost() - this.uipkgeCost()))
      readonly annualSavings = computed(() => this.monthlySavings() * 12)
      readonly uipkgeBarWidth = computed(() => Math.max(5, (this.uipkgeCost() / Math.max(1, this.traditionalCost())) * 100))
    
      get rootClass(): string {
        return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', this.className)
      }
    
      formatNumber(num: number): string {
        return formatCompactNumber(num)
      }
    
      applyPreset(preset: 'seed' | 'growth' | 'scale'): void {
        if (preset === 'seed') {
          this.mau.set(25000)
          this.qps.set(150)
          this.edgeReplicas.set(2)
        } else if (preset === 'growth') {
          this.mau.set(350000)
          this.qps.set(2500)
          this.edgeReplicas.set(5)
        } else {
          this.mau.set(2500000)
          this.qps.set(12000)
          this.edgeReplicas.set(10)
        }
      }
    }
    
  • src/app/components/blocks/pricing-usage-calculator-slider/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/pricing-usage-calculator-slider.json