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
+
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"
+ }
+ }
}
}