diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index d6a871895f4..8325d905e18 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5568,6 +5568,9 @@ "domainVerified": { "message": "Domain verified" }, + "duplicateDomainError": { + "message": "You can't claim the same domain twice." + }, "domainNotVerified": { "message": "$DOMAIN$ not verified. Check your DNS record.", "placeholders": { @@ -5577,5 +5580,4 @@ } } } - } diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts index bce006ae006..4fcc521ee64 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts @@ -11,6 +11,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { OrganizationDomainRequest } from "@bitwarden/common/services/organization-domain/requests/organization-domain.request"; import { domainNameValidator } from "./domain-name.validator"; +import { uniqueInArrayValidator } from "./unique-in-array.validator"; export interface DomainAddEditDialogData { organizationId: string; orgDomain: OrganizationDomainResponse; @@ -26,11 +27,17 @@ export class DomainAddEditDialogComponent implements OnInit { disablePadding = false; // TODO: should invalidDomainNameMessage have something like: "'https://', 'http://', or 'www.' domain prefixes not allowed." - // TODO: write separate uniqueIn validator w/ translated msg: "You can’t claim the same domain twice." domainForm: FormGroup = this.formBuilder.group({ domainName: [ "", - [Validators.required, domainNameValidator(this.i18nService.t("invalidDomainNameMessage"))], + [ + Validators.required, + domainNameValidator(this.i18nService.t("invalidDomainNameMessage")), + uniqueInArrayValidator( + this.data.existingDomainNames, + this.i18nService.t("duplicateDomainError") + ), + ], ], txt: [{ value: null, disabled: true }], }); diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/unique-in-array.validator.ts b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/unique-in-array.validator.ts new file mode 100644 index 00000000000..4659a265959 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/unique-in-array.validator.ts @@ -0,0 +1,23 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms"; +export function uniqueInArrayValidator(values: Array, errorMessage: string): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + + if (!value) { + return null; + } + + const lowerTrimmedValue = value.toLowerCase().trim(); + + // check if the entered value is unique + if (values.some((val) => val.toLowerCase().trim() === lowerTrimmedValue)) { + return { + nonUniqueValue: { + message: errorMessage, + }, + }; + } + + return null; + }; +} diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts index 607da81becc..e8b9b4012cd 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params } from "@angular/router"; -import { concatMap, Observable, Subject, takeUntil } from "rxjs"; +import { concatMap, Observable, Subject, take, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -68,7 +68,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { const domainAddEditDialogData: DomainAddEditDialogData = { organizationId: this.organizationId, orgDomain: null, - existingDomainNames: [], + existingDomainNames: this.getExistingDomainNames(), }; this.dialogService.open(DomainAddEditDialogComponent, { @@ -80,7 +80,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { const domainAddEditDialogData: DomainAddEditDialogData = { organizationId: this.organizationId, orgDomain: orgDomain, - existingDomainNames: [], + existingDomainNames: this.getExistingDomainNames(), }; this.dialogService.open(DomainAddEditDialogComponent, { @@ -88,6 +88,15 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { }); } + private getExistingDomainNames(): Array { + let existingDomainNames: string[]; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil + this.orgDomains$.pipe(take(1)).subscribe((orgDomains: Array) => { + existingDomainNames = orgDomains.map((o) => o.domainName); + }); + return existingDomainNames; + } + ngOnDestroy(): void { this.componentDestroyed$.next(); this.componentDestroyed$.complete();