Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<p bitTypography="body1">
{{ "inviteByLinkDesc" | i18n }}
<a
bitLink
href="https://bitwarden.com/help/managing-users/#add-new-members"
target="_blank"
rel="noreferrer"
endIcon="bwi-external-link"
>
{{ "learnMore" | i18n }}
</a>
</p>

<div class="tw-flex tw-items-start tw-gap-2">
<bit-form-field class="tw-flex-1">
<bit-label
>{{ "allowedDomains" | i18n }}
<span class="tw-font-normal tw-text-sm">({{ "required" | i18n }})</span></bit-label
>
<input bitInput type="text" [formControl]="form.controls.domains" />
<bit-hint>{{ "allowedDomainsHint" | i18n }}</bit-hint>
</bit-form-field>

@if (form.dirty) {
<button
type="button"
bitButton
bitFormButton
buttonType="primary"
[bitAction]="save"
class="tw-mt-[10px]"
>
{{ "save" | i18n }}
</button>
}
</div>

@if (showCallout()) {
<bit-callout type="info">
<strong>
{{ "enterAllowedDomainsToGenerateInviteLink" | i18n }}
</strong>
</bit-callout>
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,117 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import {
ChangeDetectionStrategy,
Component,
inject,
input,
OnInit,
signal,
WritableSignal,
} from "@angular/core";
import { takeUntilDestroyed, toObservable } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";

import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
CalloutModule,
FormFieldModule,
LinkComponent,
ToastService,
} from "@bitwarden/components";
import {
OrganizationInviteLink,
OrganizationInviteLinkService,
} from "@bitwarden/organization-invite-link";
import { I18nPipe } from "@bitwarden/ui-common";

@Component({
standalone: true,
selector: "app-by-link-tab",
template: ``,
templateUrl: "by-link-tab.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
ButtonModule,
CalloutModule,
CommonModule,
FormFieldModule,
I18nPipe,
ReactiveFormsModule,
LinkComponent,
],
})
export class ByLinkTabComponent {}
export class ByLinkTabComponent implements OnInit {
readonly organizationId = input.required<OrganizationId, string>({
transform: (value: string) => value as OrganizationId,
});

private readonly accountService = inject(AccountService);
private readonly inviteLinkService = inject(OrganizationInviteLinkService);
private readonly toastService = inject(ToastService);
private readonly i18nService = inject(I18nService);
private readonly fb = inject(FormBuilder);

protected readonly inviteLink$: Observable<OrganizationInviteLink | undefined> = combineLatest([
this.accountService.activeAccount$.pipe(getUserId),
toObservable(this.organizationId),
]).pipe(switchMap(([userId, orgId]) => this.inviteLinkService.inviteLink$(userId, orgId)));

protected readonly showCallout: WritableSignal<boolean> = signal(false);

protected readonly form = this.fb.group({
domains: ["", Validators.required],
});

constructor() {
this.inviteLink$.pipe(takeUntilDestroyed()).subscribe((inviteLink) => {
if (inviteLink && !this.form.dirty) {
this.form.controls.domains.setValue(inviteLink.allowedDomains.join(", "));
this.form.markAsPristine();
}
});
}

async ngOnInit() {
this.showCallout.set(await firstValueFrom(this.inviteLink$.pipe(map((link) => link == null))));
}

readonly save = async () => {
this.form.markAllAsTouched();
if (this.form.invalid) {
return;
}

const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const rawDomains = this.form.value.domains;
if (rawDomains == null) {
throw new Error("Must provide at least one valid domain.");
}

const domains = rawDomains
.split(",")
.map((domain) => domain.trim())
.filter((domain) => domain.length > 0);

const inviteLink = await firstValueFrom(this.inviteLink$);
if (inviteLink) {
await this.inviteLinkService.updateInviteLink(userId, this.organizationId(), domains);
} else {
await this.inviteLinkService.createInviteLink(userId, this.organizationId(), domains);
}

this.form.markAsPristine();

this.showCallout.set(false);

this.toastService.showToast({
variant: "success",
message: this.i18nService.t("domainsEdited"),
});
};
}
Loading
Loading