Skip to content

[PM-17774] Build page for admin sponsored families #14243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2e4df22
Added nav item for f4e in org admin console
cturnbull-bitwarden Mar 27, 2025
4276e0e
Merge branch 'main' into billing/PM-17773/f4e-nav-item
cturnbull-bitwarden Mar 27, 2025
9aea97e
shotgun surgery for adding "useAdminSponsoredFamilies" feature from tโ€ฆ
cturnbull-bitwarden Mar 27, 2025
33f6b38
Resolved issue with members nav item also being selected when f4e is โ€ฆ
cturnbull-bitwarden Mar 27, 2025
8b3b0c6
Merge branch 'main' into billing/PM-17773/f4e-nav-item
cturnbull-bitwarden Mar 27, 2025
cb19533
Separated out billing's logic from the org layout component
cturnbull-bitwarden Mar 28, 2025
03531a5
Removed unused observable
cturnbull-bitwarden Mar 28, 2025
457682e
Merge branch 'main' into billing/PM-17773/f4e-nav-item
cturnbull-bitwarden Mar 28, 2025
6783aa4
Moved logic to existing f4e policy service and added unit tests
cturnbull-bitwarden Mar 28, 2025
2fd703d
Resolved script typescript error
cturnbull-bitwarden Mar 28, 2025
062fdeb
Merge branch 'main' into billing/PM-17773/f4e-nav-item
cturnbull-bitwarden Mar 28, 2025
8e31e8b
Resolved goofy switchMap
cturnbull-bitwarden Mar 31, 2025
2e2f4d1
Merge branch 'main' into billing/PM-17773/f4e-nav-item
cturnbull-bitwarden Mar 31, 2025
664cb0d
Add changes for the issue orgs
cyprain-okeke Apr 8, 2025
76d2bf2
Added changes for the dialog
cyprain-okeke Apr 9, 2025
2d2da8a
Rename the files properly
cyprain-okeke Apr 10, 2025
6e3b329
Remove the commented code
cyprain-okeke Apr 10, 2025
b8b5416
Change the implement to align with design
cyprain-okeke Apr 11, 2025
bfbfe8e
Add todo comments
cyprain-okeke Apr 11, 2025
d5ef998
Remove the comment todo
cyprain-okeke Apr 11, 2025
ac4d87d
Fix the uni test error
cyprain-okeke Apr 11, 2025
823e9b6
Resolve the unit test
cyprain-okeke Apr 11, 2025
f5a0389
Resolve the unit test issue
cyprain-okeke Apr 11, 2025
f6029ca
Resolve the pr comments on any and route
cyprain-okeke Apr 15, 2025
b75a539
remove the any
cyprain-okeke Apr 15, 2025
2740e78
remove the generic validator
cyprain-okeke Apr 15, 2025
12a3aec
Resolve the unit test
cyprain-okeke Apr 15, 2025
04f69bb
Resolve the wrong message
cyprain-okeke Apr 16, 2025
7e323bb
Merge branch 'main' into pm-17774
cyprain-okeke Apr 16, 2025
425b713
Merge branch 'main' into pm-17774
cyprain-okeke Apr 16, 2025
776e70a
Resolve the duplicate route
cyprain-okeke Apr 16, 2025
b3b18df
Merge branch 'main' into pm-17774
cyprain-okeke Apr 16, 2025
b2ae608
Merge branch 'main' into pm-17774
cyprain-okeke Apr 17, 2025
1edc719
Merge branch 'main' into pm-17774
cyprain-okeke Apr 17, 2025
f9e69e4
Merge branch 'main' into pm-17774
cyprain-okeke Apr 17, 2025
e49e56c
Merge branch 'main' into pm-17774
cyprain-okeke Apr 17, 2025
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
Expand Up @@ -3,9 +3,10 @@

import { canAccessMembersTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";

import { SponsoredFamiliesComponent } from "../../../billing/settings/sponsored-families.component";
import { FreeBitwardenFamiliesComponent } from "../../../billing/members/free-bitwarden-families.component";

Check warning on line 6 in apps/web/src/app/admin-console/organizations/members/members-routing.module.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/admin-console/organizations/members/members-routing.module.ts#L6

Added line #L6 was not covered by tests
import { organizationPermissionsGuard } from "../guards/org-permissions.guard";

import { canAccessSponsoredFamilies } from "./../../../billing/guards/can-access-sponsored-families.guard";

Check warning on line 9 in apps/web/src/app/admin-console/organizations/members/members-routing.module.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/admin-console/organizations/members/members-routing.module.ts#L9

Added line #L9 was not covered by tests
import { MembersComponent } from "./members.component";

const routes: Routes = [
Expand All @@ -19,8 +20,8 @@
},
{
path: "sponsored-families",
component: SponsoredFamiliesComponent,
canActivate: [organizationPermissionsGuard(canAccessMembersTab)],
component: FreeBitwardenFamiliesComponent,
canActivate: [organizationPermissionsGuard(canAccessMembersTab), canAccessSponsoredFamilies],
data: {
titleId: "sponsoredFamilies",
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { inject } from "@angular/core";

Check warning on line 1 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L1

Added line #L1 was not covered by tests
import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router";
import { firstValueFrom, switchMap, filter } from "rxjs";

Check warning on line 3 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L3

Added line #L3 was not covered by tests

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";

Check warning on line 5 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L5

Added line #L5 was not covered by tests
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { getById } from "@bitwarden/common/platform/misc";

Check warning on line 9 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L7-L9

Added lines #L7 - L9 were not covered by tests

import { FreeFamiliesPolicyService } from "../services/free-families-policy.service";

Check warning on line 11 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L11

Added line #L11 was not covered by tests

export const canAccessSponsoredFamilies: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
const freeFamiliesPolicyService = inject(FreeFamiliesPolicyService);
const organizationService = inject(OrganizationService);
const accountService = inject(AccountService);

Check warning on line 16 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L13-L16

Added lines #L13 - L16 were not covered by tests

const org = accountService.activeAccount$.pipe(

Check warning on line 18 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L18

Added line #L18 was not covered by tests
getUserId,
switchMap((userId) => organizationService.organizations$(userId)),

Check warning on line 20 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L20

Added line #L20 was not covered by tests
getById(route.params.organizationId),
filter((org): org is Organization => org !== undefined),

Check warning on line 22 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L22

Added line #L22 was not covered by tests
);

return await firstValueFrom(freeFamiliesPolicyService.showSponsoredFamiliesDropdown$(org));

Check warning on line 25 in apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/guards/can-access-sponsored-families.guard.ts#L25

Added line #L25 was not covered by tests
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<form>
<bit-dialog>
<span bitDialogTitle>{{ "addSponsorship" | i18n }}</span>

<div bitDialogContent>
<form [formGroup]="sponsorshipForm">
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="tw-col-span-12">
<bit-form-field>
<bit-label>{{ "email" | i18n }}:</bit-label>
<input
bitInput
inputmode="email"
formControlName="sponsorshipEmail"
[attr.aria-invalid]="sponsorshipEmailControl.invalid"
appInputStripSpaces
/>
</bit-form-field>
</div>
<div class="tw-col-span-12">
<bit-form-field>
<bit-label>{{ "notes" | i18n }}:</bit-label>
<input
bitInput
inputmode="text"
formControlName="sponsorshipNote"
[attr.aria-invalid]="sponsorshipNoteControl.invalid"
appInputStripSpaces
/>
</bit-form-field>
</div>
</div>
</form>
</div>

<ng-container bitDialogFooter>
<button bitButton bitFormButton type="button" buttonType="primary" (click)="save()">
{{ "save" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" [bitDialogClose]="false">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>
135 changes: 135 additions & 0 deletions apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core";
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from "@angular/forms";
import { firstValueFrom, map } from "rxjs";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ButtonModule, DialogModule, DialogService, FormFieldModule } from "@bitwarden/components";

interface RequestSponsorshipForm {
sponsorshipEmail: FormControl<string | null>;
sponsorshipNote: FormControl<string | null>;
}

export interface AddSponsorshipDialogResult {
action: AddSponsorshipDialogAction;
value: Partial<AddSponsorshipFormValue> | null;
}

interface AddSponsorshipFormValue {
sponsorshipEmail: string;
sponsorshipNote: string;
status: string;
}

enum AddSponsorshipDialogAction {
Saved = "saved",
Canceled = "canceled",
}

@Component({
templateUrl: "add-sponsorship-dialog.component.html",
standalone: true,
imports: [
JslibModule,
ButtonModule,
DialogModule,
FormsModule,
ReactiveFormsModule,
FormFieldModule,
],
})
export class AddSponsorshipDialogComponent {
sponsorshipForm: FormGroup<RequestSponsorshipForm>;
loading = false;

Check warning on line 55 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L55

Added line #L55 was not covered by tests

constructor(
private dialogRef: DialogRef<AddSponsorshipDialogResult>,
private formBuilder: FormBuilder,
private accountService: AccountService,
private i18nService: I18nService,

Check warning on line 61 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L61

Added line #L61 was not covered by tests
) {
this.sponsorshipForm = this.formBuilder.group<RequestSponsorshipForm>({

Check warning on line 63 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L63

Added line #L63 was not covered by tests
sponsorshipEmail: new FormControl<string | null>("", {
validators: [Validators.email, Validators.required],
asyncValidators: [this.validateNotCurrentUserEmail.bind(this)],
updateOn: "change",
}),
sponsorshipNote: new FormControl<string | null>("", {}),
});
}

static open(dialogService: DialogService): DialogRef<AddSponsorshipDialogResult> {
return dialogService.open<AddSponsorshipDialogResult>(AddSponsorshipDialogComponent);

Check warning on line 74 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L74

Added line #L74 was not covered by tests
}

protected async save() {
if (this.sponsorshipForm.invalid) {
return;

Check warning on line 79 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L79

Added line #L79 was not covered by tests
}

this.loading = true;

Check warning on line 82 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L82

Added line #L82 was not covered by tests
// TODO: This is a mockup implementation - needs to be updated with actual API integration
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call

Check warning on line 84 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L84

Added line #L84 was not covered by tests

const formValue = this.sponsorshipForm.getRawValue();
const dialogValue: Partial<AddSponsorshipFormValue> = {

Check warning on line 87 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L86-L87

Added lines #L86 - L87 were not covered by tests
status: "Sent",
sponsorshipEmail: formValue.sponsorshipEmail ?? "",
sponsorshipNote: formValue.sponsorshipNote ?? "",
};

this.dialogRef.close({

Check warning on line 93 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L93

Added line #L93 was not covered by tests
action: AddSponsorshipDialogAction.Saved,
value: dialogValue,
});

this.loading = false;

Check warning on line 98 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L98

Added line #L98 was not covered by tests
}

protected close = () => {
this.dialogRef.close({ action: AddSponsorshipDialogAction.Canceled, value: null });

Check warning on line 102 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L101-L102

Added lines #L101 - L102 were not covered by tests
};

get sponsorshipEmailControl() {
return this.sponsorshipForm.controls.sponsorshipEmail;

Check warning on line 106 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L106

Added line #L106 was not covered by tests
}

get sponsorshipNoteControl() {
return this.sponsorshipForm.controls.sponsorshipNote;

Check warning on line 110 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L110

Added line #L110 was not covered by tests
}

private async validateNotCurrentUserEmail(
control: AbstractControl,
): Promise<ValidationErrors | null> {
const value = control.value;

Check warning on line 116 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L116

Added line #L116 was not covered by tests
if (!value) {
return null;

Check warning on line 118 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L118

Added line #L118 was not covered by tests
}

const currentUserEmail = await firstValueFrom(

Check warning on line 121 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L121

Added line #L121 was not covered by tests
this.accountService.activeAccount$.pipe(map((a) => a?.email ?? "")),
);

if (!currentUserEmail) {
return null;

Check warning on line 126 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L126

Added line #L126 was not covered by tests
}

if (value.toLowerCase() === currentUserEmail.toLowerCase()) {
return { currentUserEmail: true };

Check warning on line 130 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L130

Added line #L130 was not covered by tests
}

return null;

Check warning on line 133 in apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts#L133

Added line #L133 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<app-header>
<button type="button" (click)="addSponsorship()" bitButton buttonType="primary">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "addSponsorship" | i18n }}
</button>
</app-header>

<bit-tab-group [(selectedIndex)]="tabIndex">
<bit-tab [label]="'sponsoredBitwardenFamilies' | i18n">
<app-organization-sponsored-families
[sponsoredFamilies]="sponsoredFamilies"
(removeSponsorshipEvent)="removeSponsorhip($event)"
></app-organization-sponsored-families>
</bit-tab>

<bit-tab [label]="'memberFamilies' | i18n">
<app-organization-member-families
[memberFamilies]="sponsoredFamilies"
></app-organization-member-families>
</bit-tab>
</bit-tab-group>

<p class="tw-px-4" bitTypography="body2">{{ "sponsoredFamiliesRemoveActiveSponsorship" | i18n }}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";

import { DialogService } from "@bitwarden/components";

import { FreeFamiliesPolicyService } from "../services/free-families-policy.service";

import {
AddSponsorshipDialogComponent,
AddSponsorshipDialogResult,
} from "./add-sponsorship-dialog.component";
import { SponsoredFamily } from "./types/sponsored-family";

@Component({
selector: "app-free-bitwarden-families",
templateUrl: "free-bitwarden-families.component.html",
})
export class FreeBitwardenFamiliesComponent implements OnInit {
tabIndex = 0;
sponsoredFamilies: SponsoredFamily[] = [];

Check warning on line 22 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L21-L22

Added lines #L21 - L22 were not covered by tests

constructor(
private router: Router,
private dialogService: DialogService,
private freeFamiliesPolicyService: FreeFamiliesPolicyService,

Check warning on line 27 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L27

Added line #L27 was not covered by tests
) {}

async ngOnInit() {
await this.preventAccessToFreeFamiliesPage();

Check warning on line 31 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L31

Added line #L31 was not covered by tests
}

async addSponsorship() {
const addSponsorshipDialogRef: DialogRef<AddSponsorshipDialogResult> =
AddSponsorshipDialogComponent.open(this.dialogService);

Check warning on line 36 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L36

Added line #L36 was not covered by tests

const dialogRef = await firstValueFrom(addSponsorshipDialogRef.closed);

Check warning on line 38 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L38

Added line #L38 was not covered by tests

if (dialogRef?.value) {
this.sponsoredFamilies = [dialogRef.value, ...this.sponsoredFamilies];

Check warning on line 41 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L41

Added line #L41 was not covered by tests
}
}

removeSponsorhip(sponsorship: any) {
const index = this.sponsoredFamilies.findIndex(
(e) => e.sponsorshipEmail == sponsorship.sponsorshipEmail,

Check warning on line 47 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L46-L47

Added lines #L46 - L47 were not covered by tests
);
this.sponsoredFamilies.splice(index, 1);

Check warning on line 49 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L49

Added line #L49 was not covered by tests
}

private async preventAccessToFreeFamiliesPage() {
const showFreeFamiliesPage = await firstValueFrom(

Check warning on line 53 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L53

Added line #L53 was not covered by tests
this.freeFamiliesPolicyService.showFreeFamilies$,
);

if (!showFreeFamiliesPage) {
await this.router.navigate(["/"]);
return;

Check warning on line 59 in apps/web/src/app/billing/members/free-bitwarden-families.component.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/billing/members/free-bitwarden-families.component.ts#L58-L59

Added lines #L58 - L59 were not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<bit-container>
<ng-container>
<p bitTypography="body1">
{{ "membersWithSponsoredFamilies" | i18n }}
</p>

<h2 bitTypography="h2" class="">{{ "memberFamilies" | i18n }}</h2>

@if (loading) {
<ng-container>
<i class="bwi bwi-spinner bwi-spin tw-text-muted" title="{{ 'loading' | i18n }}"></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
}

@if (!loading && memberFamilies?.length > 0) {
<ng-container>
<bit-table>
<ng-container header>
<tr>
<th bitCell>{{ "member" | i18n }}</th>
<th bitCell>{{ "status" | i18n }}</th>
<th bitCell></th>
</tr>
</ng-container>
<ng-template body alignContent="middle">
@for (o of memberFamilies; let i = $index; track i) {
<ng-container>
<tr bitRow>
<td bitCell>{{ o.sponsorshipEmail }}</td>
<td bitCell class="tw-text-success">{{ o.status }}</td>
</tr>
</ng-container>
}
</ng-template>
</bit-table>
<hr class="mt-0" />
</ng-container>
} @else {
<div class="tw-my-5 tw-py-5 tw-flex tw-flex-col tw-items-center">
<img class="tw-w-32" src="./../../../images/search.svg" alt="Search" />
<h4 class="mt-3" bitTypography="h4">{{ "noMemberFamilies" | i18n }}</h4>
<p bitTypography="body2">{{ "noMemberFamiliesDescription" | i18n }}</p>
</div>
}
</ng-container>
</bit-container>
Loading
Loading