Menu

Change language

GitHub repo

Seat Pricing Matrix

blockmarketing

Comprehensive enterprise pricing workbench with monthly/annual billing cycle toggle, currency selector (USD/EUR/GBP), team seat simulator slider, dynamic add-on calculators, and an expandable side-by-side feature comparison matrix.

Installation

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

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

Pricing01FeatureRow
interface Pricing01FeatureRow {
  name: string
  starter: string
  team: string
  enterprise: string
}
Pricing01ComparisonGroup
interface Pricing01ComparisonGroup {
  category: string
  features: Pricing01FeatureRow[]
}

Used by

Files installed (2)

  • src/app/components/blocks/pricing-01/pricing-01.component.ts27.9 kB
    // Seat pricing matrix: monthly/annual billing toggle, currency selector (USD/EUR/GBP),
    // team seat simulator slider, dynamic add-on calculators, and an expandable side-by-side
    // feature comparison matrix. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { ArrowRight, Check, ChevronDown, ChevronUp, LucideAngularModule, ShieldCheck, Users, Zap } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { UiSliderComponent } from '@/ui/slider/slider.component'
    import { UiSwitchComponent } from '@/ui/switch/switch.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component'
    
    export type Pricing01BillingCycle = 'monthly' | 'yearly'
    export type Pricing01Currency = 'USD' | 'EUR' | 'GBP'
    
    const currencySymbols: Record<Pricing01Currency, string> = {
      USD: '$',
      EUR: '€',
      GBP: '£',
    }
    
    const currencyRates: Record<Pricing01Currency, number> = {
      USD: 1.0,
      EUR: 0.92,
      GBP: 0.79,
    }
    
    export interface Pricing01FeatureRow {
      name: string
      starter: string
      team: string
      enterprise: string
    }
    
    export interface Pricing01ComparisonGroup {
      category: string
      features: Pricing01FeatureRow[]
    }
    
    const comparisonMatrix: Pricing01ComparisonGroup[] = [
      {
        category: 'Platform & Compute Core',
        features: [
          { name: 'Active Team Members', starter: 'Up to 10', team: 'Unlimited', enterprise: 'Unlimited + Org Units' },
          { name: 'Monthly API Requests', starter: '100,000 / mo', team: '2,500,000 / mo', enterprise: 'Custom Unlimited' },
          { name: 'Workflow Automations', starter: '10 active', team: '100 active', enterprise: 'Unlimited real-time' },
          { name: 'Data Retention History', starter: '30 days', team: '365 days', enterprise: '7 years immutable' },
        ],
      },
      {
        category: 'Security & Enterprise Governance',
        features: [
          {
            name: 'SOC 2 Type II & ISO 27001',
            starter: 'Standard',
            team: 'Included',
            enterprise: 'Included + Auditor Portal',
          },
          { name: 'SAML SSO & SCIM Provisioning', starter: '—', team: 'Google / Okta', enterprise: 'Custom IdP + SCIM v2' },
          {
            name: 'Role-Based Access (RBAC)',
            starter: '3 predefined roles',
            team: 'Granular permissions',
            enterprise: 'Custom policy engine',
          },
          {
            name: 'Immutable Audit Trail Logs',
            starter: '—',
            team: '90 days exportable',
            enterprise: 'Real-time SIEM streaming',
          },
        ],
      },
      {
        category: 'Support & Success SLAs',
        features: [
          {
            name: 'Support Channel',
            starter: 'Community & Email',
            team: 'Priority Email + Chat',
            enterprise: 'Dedicated Private Slack',
          },
          {
            name: 'First-Response SLA',
            starter: '24 business hours',
            team: '4 business hours',
            enterprise: '< 15 mins (24/7/365)',
          },
          { name: 'Uptime SLA Guarantee', starter: '99.9%', team: '99.95%', enterprise: '99.99% financially backed' },
          { name: 'Dedicated Solutions Architect', starter: '—', team: '—', enterprise: 'Assigned Principal Engineer' },
        ],
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-pricing-01, [ui-pricing-01]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardFooterComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiSeparatorComponent,
        UiSliderComponent,
        UiSwitchComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
        UiToggleGroupComponent,
        UiToggleGroupItemComponent,
      ],
      template: `
        <section data-slot="pricing-01" [class]="rootClass">
          <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
            <!-- Section Header -->
            <div class="mx-auto max-w-3xl space-y-4 text-center">
              <div
                class="border-primary/20 bg-primary/5 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium shadow-2xs"
              >
                <lucide-icon [img]="ShieldCheck" class="size-3.5" />
                <span>Predictable Enterprise Pricing</span>
              </div>
              <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
                Scale effortlessly without seat tax surprises
              </h2>
              <p class="text-muted-foreground text-base sm:text-lg">
                Zero setup fees, transparent volume discounts, and instant self-serve provisioning. Switch or cancel plans
                anytime.
              </p>
    
              <!-- Billing Cycle & Currency Switcher Toolbar -->
              <div class="mt-8 flex flex-wrap items-center justify-center gap-4 pt-2">
                <!-- Monthly vs Yearly -->
                <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs">
                  <ui-toggle-group type="single" [value]="billingCycle()" (valueChange)="setBillingCycle($event)">
                    <button ui-toggle-group-item value="monthly" class="px-3.5 py-1.5 text-xs font-medium">Monthly</button>
                    <button
                      ui-toggle-group-item
                      value="yearly"
                      class="flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium"
                    >
                      <span>Annual</span>
                      <span
                        ui-badge
                        variant="secondary"
                        class="bg-primary/10 text-primary border-primary/20 px-1.5 py-0 text-xs"
                      >
                        Save 20%
                      </span>
                    </button>
                  </ui-toggle-group>
                </div>
    
                <!-- Currency Selector -->
                <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs">
                  <ui-toggle-group type="single" [value]="currency()" (valueChange)="setCurrency($event)">
                    <button ui-toggle-group-item value="USD" class="px-2.5 py-1 font-mono text-xs font-medium">
                      USD ($)
                    </button>
                    <button ui-toggle-group-item value="EUR" class="px-2.5 py-1 font-mono text-xs font-medium">
                      EUR (€)
                    </button>
                    <button ui-toggle-group-item value="GBP" class="px-2.5 py-1 font-mono text-xs font-medium">
                      GBP (£)
                    </button>
                  </ui-toggle-group>
                </div>
              </div>
    
              <!-- Interactive Team Seat Simulator Bar -->
              <div class="border-border bg-card mx-auto mt-6 max-w-xl rounded-xl border p-4 shadow-2xs">
                <div class="flex flex-wrap items-center justify-between gap-2 text-xs">
                  <div class="text-foreground flex items-center gap-2 font-medium">
                    <lucide-icon [img]="Users" class="text-primary size-4" />
                    <span>Simulate Team Size:</span>
                  </div>
                  <div class="flex items-center gap-2">
                    <span class="bg-muted text-foreground rounded px-2 py-0.5 font-mono text-xs font-semibold">
                      {{ currentSeats() }} {{ currentSeats() === 1 ? 'seat' : 'seats' }}
                    </span>
                    @if (billingCycle() === 'yearly' && annualSavingsTeam() > 0) {
                      <span class="text-success text-xs font-medium">
                        (Saves ~{{ sym() }}{{ annualSavingsTeam().toLocaleString() }}/yr on Team plan)
                      </span>
                    }
                  </div>
                </div>
                <div class="mt-3">
                  <ui-slider
                    [value]="[currentSeats()]"
                    [min]="1"
                    [max]="50"
                    [step]="1"
                    (valueChange)="onSeatsChange($event)"
                    class="cursor-pointer"
                  />
                </div>
                <div class="text-muted-foreground mt-2 flex justify-between font-mono text-xs">
                  <span>1 seat</span>
                  <span>25 seats</span>
                  <span>50+ seats</span>
                </div>
              </div>
            </div>
    
            <!-- Plan Cards Grid -->
            <div class="mt-12 grid grid-cols-1 gap-8 lg:grid-cols-3 lg:items-stretch">
              <!-- Tier 1: Starter -->
              <div
                ui-card
                class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md"
              >
                <div>
                  <div ui-card-header class="pb-4">
                    <div class="flex items-center justify-between">
                      <span ui-badge variant="outline" class="font-mono text-xs">Bootstrap & Indie</span>
                    </div>
                    <h3 ui-card-title class="text-foreground text-xl font-bold tracking-tight">Starter Studio</h3>
                    <p ui-card-description class="text-muted-foreground text-xs">
                      For agile squads and early-stage product teams validating core workflows.
                    </p>
    
                    <div class="mt-6 space-y-1">
                      <div class="flex items-baseline gap-1.5">
                        <span class="text-foreground text-4xl font-bold tracking-tight">
                          {{ sym() }}{{ starterUnit() }}
                        </span>
                        <span class="text-muted-foreground text-xs font-medium">/ seat / month</span>
                      </div>
                      <div class="text-muted-foreground font-mono text-xs">
                        Est. {{ sym() }}{{ starterMonthlyTotal().toLocaleString() }}/mo for {{ currentSeats() }}
                        {{ currentSeats() === 1 ? 'seat' : 'seats' }}
                      </div>
                    </div>
                  </div>
    
                  <div ui-card-content class="space-y-4 pt-2">
                    <div ui-separator></div>
                    <div class="space-y-2.5">
                      <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Included features</p>
                      <ul class="text-foreground space-y-2.5 text-xs">
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Up to 10 team seats & unlimited guests</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>100,000 monthly API event calls</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>30-day continuous audit & revision history</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Standard Community & Email support (24h)</span>
                        </li>
                        <li class="text-muted-foreground flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-muted-foreground/50 mt-0.5 size-4 shrink-0" />
                          <span>Community Discord access & quickstarts</span>
                        </li>
                      </ul>
                    </div>
                  </div>
                </div>
    
                <div ui-card-footer class="border-border mt-auto border-t pt-4">
                  <button ui-button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold">
                    <span>Start Free 14-Day Trial</span>
                    <lucide-icon [img]="ArrowRight" class="size-3.5" />
                  </button>
                </div>
              </div>
    
              <!-- Tier 2: Team Pro (Highlighted Flagship) -->
              <div
                ui-card
                class="border-primary bg-card ring-primary/20 relative flex flex-col justify-between shadow-lg ring-2 transition-colors duration-200 hover:shadow-xl lg:-translate-y-2"
              >
                <div class="absolute -top-3.5 left-1/2 -translate-x-1/2">
                  <span
                    ui-badge
                    class="bg-primary text-primary-foreground gap-1.5 px-3 py-0.5 text-xs font-semibold shadow-sm"
                  >
                    <lucide-icon [img]="Zap" class="size-3 fill-current" />
                    <span>Most Popular Choice</span>
                  </span>
                </div>
    
                <div>
                  <div ui-card-header class="pt-7 pb-4">
                    <div class="flex items-center justify-between">
                      <span
                        ui-badge
                        variant="secondary"
                        class="bg-primary/10 text-primary border-primary/20 font-mono text-xs"
                      >
                        Scale & High-Growth
                      </span>
                    </div>
                    <h3 ui-card-title class="text-foreground text-2xl font-bold tracking-tight">Team Scale</h3>
                    <p ui-card-description class="text-muted-foreground text-xs">
                      For fast-scaling engineering and product organizations requiring deep automation.
                    </p>
    
                    <div class="mt-6 space-y-1">
                      <div class="flex items-baseline gap-1.5">
                        <span class="text-foreground text-4xl font-bold tracking-tight"> {{ sym() }}{{ teamUnit() }} </span>
                        <span class="text-muted-foreground text-xs font-medium">/ seat / month</span>
                      </div>
                      <div class="text-primary font-mono text-xs font-medium">
                        Est. {{ sym() }}{{ teamMonthlyTotal().toLocaleString() }}/mo for {{ currentSeats() }}
                        {{ currentSeats() === 1 ? 'seat' : 'seats' }}
                      </div>
                    </div>
                  </div>
    
                  <div ui-card-content class="space-y-4 pt-2">
                    <div ui-separator></div>
                    <div class="space-y-2.5">
                      <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                        Everything in Starter, plus
                      </p>
                      <ul class="text-foreground space-y-2.5 text-xs">
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span class="font-medium">Unlimited team seats & custom workspace roles</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>2.5M monthly API calls & priority queue</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Google & Okta SAML SSO integration</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>365-day immutable compliance logs</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Priority ticketing with 4h guaranteed SLA</span>
                        </li>
                      </ul>
                    </div>
    
                    <!-- Addons Selector for Team -->
                    <div class="border-border bg-muted/30 space-y-2.5 rounded-lg border p-3">
                      <p class="text-foreground text-xs font-semibold">Optional Power Add-ons</p>
                      <div class="flex items-center justify-between text-xs">
                        <span class="text-muted-foreground">
                          24/7 Dedicated SLA (+{{ sym() }}{{ slaAddonUnit() }}/mo)
                        </span>
                        <button
                          ui-switch
                          aria-label="Toggle dedicated SLA add-on"
                          [checked]="addDedicatedSla()"
                          (checkedChange)="addDedicatedSla.set($event)"
                        ></button>
                      </div>
                      <div class="flex items-center justify-between text-xs">
                        <span class="text-muted-foreground">
                          SOC 2 Vault Streaming (+{{ sym() }}{{ vaultAddonUnit() }}/mo)
                        </span>
                        <button
                          ui-switch
                          aria-label="Toggle audit vault add-on"
                          [checked]="addAuditVault()"
                          (checkedChange)="addAuditVault.set($event)"
                        ></button>
                      </div>
                    </div>
                  </div>
                </div>
    
                <div ui-card-footer class="border-border mt-auto border-t pt-4">
                  <button ui-button class="h-10 w-full gap-1.5 text-xs font-semibold shadow-sm">
                    <span>Deploy Team Workspace</span>
                    <lucide-icon [img]="ArrowRight" class="size-3.5" />
                  </button>
                </div>
              </div>
    
              <!-- Tier 3: Enterprise Platform -->
              <div
                ui-card
                class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md"
              >
                <div>
                  <div ui-card-header class="pb-4">
                    <div class="flex items-center justify-between">
                      <span ui-badge variant="outline" class="font-mono text-xs">Enterprise & Security</span>
                    </div>
                    <h3 ui-card-title class="text-foreground text-xl font-bold tracking-tight">Enterprise Suite</h3>
                    <p ui-card-description class="text-muted-foreground text-xs">
                      Dedicated infrastructure, custom security controls, and bespoke compliance SLAs.
                    </p>
    
                    <div class="mt-6 space-y-1">
                      <div class="flex items-baseline gap-1.5">
                        <span class="text-foreground text-4xl font-bold tracking-tight">
                          {{ sym() }}{{ enterpriseUnit() }}
                        </span>
                        <span class="text-muted-foreground text-xs font-medium">/ seat / month</span>
                      </div>
                      <div class="text-muted-foreground font-mono text-xs">
                        Custom volume licensing available for 100+ seats
                      </div>
                    </div>
                  </div>
    
                  <div ui-card-content class="space-y-4 pt-2">
                    <div ui-separator></div>
                    <div class="space-y-2.5">
                      <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                        Everything in Team, plus
                      </p>
                      <ul class="text-foreground space-y-2.5 text-xs">
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Custom SCIM v2 user provisioning & directory sync</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Dedicated VPC peering & custom data residency</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>99.99% uptime SLA with financial penalty backing</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Assigned Principal Solutions Engineer & private Slack</span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" />
                          <span>Custom Master Service Agreement (MSA) & DPA</span>
                        </li>
                      </ul>
                    </div>
                  </div>
                </div>
    
                <div ui-card-footer class="border-border mt-auto border-t pt-4">
                  <button ui-button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold">
                    <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" />
                    <span>Contact Solutions Team</span>
                  </button>
                </div>
              </div>
            </div>
    
            <!-- Collapsible Feature Matrix Trigger -->
            <div class="mt-12 text-center">
              <button
                ui-button
                variant="ghost"
                size="sm"
                class="text-foreground hover:bg-muted gap-2 text-xs font-semibold"
                (click)="toggleComparison()"
              >
                <span>{{
                  showComparisonTable() ? 'Hide Detailed Feature Comparison' : 'Compare All Features & Enterprise Limits'
                }}</span>
                @if (showComparisonTable()) {
                  <lucide-icon [img]="ChevronUp" class="size-4" />
                } @else {
                  <lucide-icon [img]="ChevronDown" class="size-4" />
                }
              </button>
            </div>
    
            <!-- Feature Comparison Matrix Table -->
            @if (showComparisonTable()) {
              <div class="border-border bg-card mt-8 overflow-hidden rounded-xl border shadow-xs">
                <div
                  class="border-border bg-muted/20 flex flex-wrap items-center justify-between gap-4 border-b p-4 sm:p-6"
                >
                  <div>
                    <h3 class="text-foreground text-base font-bold">Detailed Specification & Limits</h3>
                    <p class="text-muted-foreground text-xs">Comprehensive side-by-side breakdown across every tier.</p>
                  </div>
                  <span ui-badge variant="outline" class="font-mono text-xs"
                    >All plans include SSL & automated backups</span
                  >
                </div>
    
                <div class="overflow-x-auto">
                  <table ui-table>
                    <thead ui-table-header>
                      <tr ui-table-row class="bg-muted/40">
                        <th ui-table-head class="text-foreground w-[34%] text-xs font-semibold">Capabilities</th>
                        <th ui-table-head class="text-foreground w-[22%] text-xs font-semibold">Starter Studio</th>
                        <th ui-table-head class="text-primary w-[22%] text-xs font-bold font-semibold">Team Scale</th>
                        <th ui-table-head class="text-foreground w-[22%] text-xs font-semibold">Enterprise Suite</th>
                      </tr>
                    </thead>
                    <tbody ui-table-body>
                      @for (group of comparisonGroups; track group.category) {
                        <tr ui-table-row class="bg-muted/60 text-muted-foreground text-xs font-semibold">
                          <td ui-table-cell colspan="4" class="py-2.5 font-mono text-xs tracking-wider uppercase">
                            {{ group.category }}
                          </td>
                        </tr>
                        @for (feat of group.features; track feat.name) {
                          <tr ui-table-row class="text-xs">
                            <td ui-table-cell class="text-foreground py-3 font-medium">{{ feat.name }}</td>
                            <td ui-table-cell class="text-muted-foreground py-3">{{ feat.starter }}</td>
                            <td ui-table-cell class="text-foreground bg-primary/5 py-3 font-medium">{{ feat.team }}</td>
                            <td ui-table-cell class="text-foreground py-3">{{ feat.enterprise }}</td>
                          </tr>
                        }
                      }
                    </tbody>
                  </table>
                </div>
              </div>
            }
          </div>
        </section>
      `,
    })
    export class UiPricing01Component {
      protected readonly ArrowRight = ArrowRight
      protected readonly Check = Check
      protected readonly ChevronDown = ChevronDown
      protected readonly ChevronUp = ChevronUp
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Users = Users
      protected readonly Zap = Zap
    
      protected readonly comparisonGroups = comparisonMatrix
    
      @Input() set initialBillingCycle(v: Pricing01BillingCycle) {
        if (v) this.billingCycle.set(v)
      }
      @Input() set initialCurrency(v: Pricing01Currency) {
        if (v) this.currency.set(v)
      }
      @Input() set initialShowComparison(v: boolean) {
        this.showComparisonTable.set(!!v)
      }
      @Input('class') className?: string
    
      readonly billingCycle = signal<Pricing01BillingCycle>('yearly')
      readonly currency = signal<Pricing01Currency>('USD')
      readonly teamSeats = signal<number[]>([12])
      readonly addDedicatedSla = signal(false)
      readonly addAuditVault = signal(false)
      readonly showComparisonTable = signal(false)
    
      readonly rates = computed(() => currencyRates[this.currency()])
      readonly sym = computed(() => currencySymbols[this.currency()])
    
      readonly starterBase = computed(() => (this.billingCycle() === 'yearly' ? 12 : 15))
      readonly teamBase = computed(() => (this.billingCycle() === 'yearly' ? 28 : 35))
      readonly enterpriseBase = computed(() => (this.billingCycle() === 'yearly' ? 68 : 85))
    
      readonly slaCost = computed(() => (this.addDedicatedSla() ? 99 : 0))
      readonly vaultCost = computed(() => (this.addAuditVault() ? 49 : 0))
    
      readonly currentSeats = computed(() => this.teamSeats()[0] ?? 1)
    
      readonly starterUnit = computed(() => Math.round(this.starterBase() * this.rates()))
      readonly teamUnit = computed(() => Math.round(this.teamBase() * this.rates()))
      readonly enterpriseUnit = computed(() => Math.round(this.enterpriseBase() * this.rates()))
      readonly slaAddonUnit = computed(() => Math.round(99 * this.rates()))
      readonly vaultAddonUnit = computed(() => Math.round(49 * this.rates()))
    
      readonly starterMonthlyTotal = computed(() => {
        const base = this.starterBase() * this.currentSeats()
        return Math.round((base + (this.addAuditVault() ? 29 : 0)) * this.rates())
      })
    
      readonly teamMonthlyTotal = computed(() => {
        const base = this.teamBase() * this.currentSeats()
        return Math.round((base + this.slaCost() + this.vaultCost()) * this.rates())
      })
    
      readonly enterpriseMonthlyTotal = computed(() => {
        const base = this.enterpriseBase() * this.currentSeats()
        return Math.round((base + this.slaCost() + this.vaultCost()) * this.rates())
      })
    
      readonly annualSavingsTeam = computed(() => {
        const monthlyCost = 35 * this.currentSeats() * 12
        const yearlyCost = 28 * this.currentSeats() * 12
        return Math.round((monthlyCost - yearlyCost) * this.rates())
      })
    
      get rootClass(): string {
        return cn('bg-background w-full py-16 sm:py-24', this.className)
      }
    
      setBillingCycle(value: string): void {
        if (value === 'monthly' || value === 'yearly') this.billingCycle.set(value)
      }
    
      setCurrency(value: string): void {
        if (value === 'USD' || value === 'EUR' || value === 'GBP') this.currency.set(value)
      }
    
      onSeatsChange(value: number[]): void {
        if (value.length > 0) this.teamSeats.set(value)
      }
    
      toggleComparison(): void {
        this.showComparisonTable.update((v) => !v)
      }
    }
    
  • src/app/components/blocks/pricing-01/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/pricing-01.json