diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html index 3a29bd0192..bb3df59192 100644 --- a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html @@ -1 +1,92 @@ -

Secrets Manager Adjust Subscription Component

+
+ + {{ "subscriptionSeats" | i18n }} + + + {{ "total" | i18n }}: + {{ formGroup.value.seatCount }} × {{ options.seatPrice | currency }} = + {{ seatTotal | currency }} / {{ options.interval | i18n }} + + + + {{ "limitSubscription" | i18n }} + + + {{ "limitSmSubscriptionDesc" | i18n }} + + + + {{ "maxSeatLimit" | i18n }} + + + {{ "total" | i18n }}: + {{ formGroup.value.seatLimit || 0 }} × {{ options.seatPrice | currency }} = + {{ maxSeatTotal | currency }} / {{ options.interval | i18n }} + + + + {{ "additionalServiceAccounts" | i18n }} + + +
+ {{ + "additionalServiceAccountsDesc" + | i18n : options.baseServiceAccountCount : (monthlyServiceAccountPrice | currency) + }} +
+
+ {{ "total" | i18n }}: + {{ formGroup.value.serviceAccountCount || 0 }} × + {{ options.additionalServiceAccountPrice | currency }} = + {{ serviceAccountTotal | currency }} / {{ options.interval | i18n }} +
+
+
+ + {{ "limitServiceAccounts" | i18n }} + + + {{ "limitServiceAccountsDesc" | i18n }} + + + + {{ "serviceAccountLimit" | i18n }} + + + {{ "total" | i18n }}: + {{ formGroup.value.serviceAccountLimit || 0 }} × + {{ options.additionalServiceAccountPrice | currency }} = + {{ maxServiceAccountTotal | currency }} / {{ options.interval | i18n }} + + + + +
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts index 7efe724c5a..fdbdef061d 100644 --- a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts @@ -1,7 +1,118 @@ -import { Component } from "@angular/core"; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { Subject, takeUntil } from "rxjs"; + +export interface SecretsManagerSubscriptionOptions { + interval: "year" | "month"; + seatCount: number; + seatLimit: number; + seatPrice: number; + + /** + * The number of service accounts that are included in the base subscription. + */ + baseServiceAccountCount: number; + + additionalServiceAccountCount: number; + additionalServiceAccountLimit: number; + additionalServiceAccountPrice: number; +} @Component({ selector: "app-sm-adjust-subscription", templateUrl: "sm-adjust-subscription.component.html", }) -export class SecretsManagerAdjustSubscriptionComponent {} +export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDestroy { + @Input() organizationId: string; + @Input() options: SecretsManagerSubscriptionOptions; + @Output() onAdjusted = new EventEmitter(); + + private destroy$ = new Subject(); + + formGroup = this.formBuilder.group({ + seatCount: [0, [Validators.required, Validators.min(0)]], + limitSeats: [false], + seatLimit: [null as number | null], + serviceAccountCount: [0, [Validators.required, Validators.min(0)]], + limitServiceAccounts: [false], + serviceAccountLimit: [null as number | null], + }); + + get monthlyServiceAccountPrice(): number { + return this.options.interval == "month" + ? this.options.additionalServiceAccountPrice + : Math.round((this.options.additionalServiceAccountPrice / 12 + Number.EPSILON) * 100) / 100; + } + + get serviceAccountTotal(): number { + return Math.abs( + this.formGroup.value.serviceAccountCount * this.options.additionalServiceAccountPrice + ); + } + + get seatTotal(): number { + return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice); + } + + get maxServiceAccountTotal(): number { + return Math.abs( + (this.formGroup.value.serviceAccountLimit ?? 0) * this.options.additionalServiceAccountPrice + ); + } + + get maxSeatTotal(): number { + return Math.abs((this.formGroup.value.seatLimit ?? 0) * this.options.seatPrice); + } + + constructor(private formBuilder: FormBuilder) {} + + ngOnInit() { + this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { + const seatLimitControl = this.formGroup.controls.seatLimit; + const serviceAccountLimitControl = this.formGroup.controls.serviceAccountLimit; + + if (value.limitSeats) { + seatLimitControl.setValidators([Validators.required, Validators.min(value.seatCount)]); + seatLimitControl.enable({ emitEvent: false }); + } else { + seatLimitControl.disable({ emitEvent: false }); + } + + if (value.limitServiceAccounts) { + serviceAccountLimitControl.setValidators([ + Validators.required, + Validators.min(value.serviceAccountCount), + ]); + serviceAccountLimitControl.enable({ emitEvent: false }); + } else { + serviceAccountLimitControl.disable({ emitEvent: false }); + } + }); + + this.formGroup.patchValue({ + seatCount: this.options.seatCount, + seatLimit: this.options.seatLimit, + serviceAccountCount: this.options.additionalServiceAccountCount, + serviceAccountLimit: this.options.additionalServiceAccountLimit, + limitSeats: this.options.seatLimit != null, + limitServiceAccounts: this.options.additionalServiceAccountLimit != null, + }); + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + // TODO: Make the request to update the subscription + + this.onAdjusted.emit(); + }; + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 72b3165e03..2b0b9b5b2f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3309,6 +3309,9 @@ "limitSubscriptionDesc": { "message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members." }, + "limitSmSubscriptionDesc": { + "message": "Set a seat limit for your Secrets Manger subscription. Once this limit is reached, you will not be able to invite new members." + }, "maxSeatLimit": { "message": "Seat Limit (optional)", "description": "Upper limit of seats to allow through autoscaling" @@ -6911,5 +6914,30 @@ }, "freeOrganization": { "message": "Free Organization" + }, + "additionalServiceAccounts": { + "message": "Additional Service Accounts" + }, + "limitServiceAccounts": { + "message": "Limit service accounts (optional)" + }, + "limitServiceAccountsDesc": { + "message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts." + }, + "serviceAccountLimit": { + "message": "Service account limit (optional)" + }, + "additionalServiceAccountsDesc": { + "message": "Your plan comes with $COUNT$ service accounts. You can add additional service accounts for $COST$ per month.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + }, + "cost": { + "content": "$2", + "example": "$0.50" + } + } } }