1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-28 12:45:45 +01:00

AC-2401 Migrate sponsored families component (#8874)

* AC-2401 Migrate sponsored families component

* AC-2401 Removed unused method
This commit is contained in:
KiruthigaManivannan 2024-05-24 19:00:06 +05:30 committed by GitHub
parent 091c8ccd46
commit 4e9db9057b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 111 additions and 90 deletions

View File

@ -3,103 +3,86 @@
<bit-container>
<ng-container *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="!loading">
<p>
<p bitTypography="body1">
{{ "sponsoredFamiliesEligible" | i18n }}
</p>
<div>
<div bitTypography="body1">
{{ "sponsoredFamiliesInclude" | i18n }}:
<ul class="inset-list">
<ul class="tw-list-outside">
<li>{{ "sponsoredFamiliesPremiumAccess" | i18n }}</li>
<li>{{ "sponsoredFamiliesSharedCollections" | i18n }}</li>
</ul>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="sponsorshipForm"
ngNativeValidate
*ngIf="anyOrgsAvailable$ | async"
>
<div class="form-group col-7">
<label for="availableSponsorshipOrg">{{ "familiesSponsoringOrgSelect" | i18n }}</label>
<select
id="availableSponsorshipOrg"
name="Available Sponsorship Organization"
formControlName="selectedSponsorshipOrgId"
class="form-control"
required
>
<option disabled="true" value="">-- {{ "select" | i18n }} --</option>
<option *ngFor="let o of availableSponsorshipOrgs$ | async" [ngValue]="o.id">
{{ o.name }}
</option>
</select>
</div>
<div class="form-group col-7">
<label for="sponsorshipEmail">{{ "sponsoredFamiliesEmail" | i18n }}:</label>
<input
id="sponsorshipEmail"
class="form-control"
inputmode="email"
formControlName="sponsorshipEmail"
name="sponsorshipEmail"
required
[attr.aria-invalid]="sponsorshipEmailControl.invalid"
/>
<small
aria-errormessage="sponsorshipEmail"
*ngIf="sponsorshipEmailControl.errors?.notAllowedValue"
class="error-inline"
role="alert"
>
<i class="bwi bwi-error" aria-hidden="true"></i>
{{ "cannotSponsorSelf" | i18n }}
</small>
<small
aria-errormessage="sponsorshipEmail"
*ngIf="sponsorshipEmailControl.errors?.email"
class="error-inline"
role="alert"
>
<i class="bwi bwi-error" aria-hidden="true"></i>
{{ "invalidEmail" | i18n }}
</small>
</div>
<div class="form-group col-7">
<button class="btn btn-primary btn-submit mt-2" type="submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "redeem" | i18n }}</span>
</button>
<form [formGroup]="sponsorshipForm" [bitSubmit]="submit" *ngIf="anyOrgsAvailable$ | async">
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="tw-col-span-7">
<bit-form-field>
<bit-label>{{ "familiesSponsoringOrgSelect" | i18n }}</bit-label>
<bit-select
id="availableSponsorshipOrg"
name="Available Sponsorship Organization"
formControlName="selectedSponsorshipOrgId"
>
<bit-option
[disabled]="true"
[value]=""
[label]="'--' + ('select' | i18n) + '--'"
></bit-option>
<bit-option
*ngFor="let o of availableSponsorshipOrgs$ | async"
[value]="o.id"
[label]="o.name"
></bit-option>
</bit-select>
</bit-form-field>
</div>
<div class="tw-col-span-7">
<bit-form-field>
<bit-label>{{ "sponsoredFamiliesEmail" | i18n }}:</bit-label>
<input
bitInput
inputmode="email"
formControlName="sponsorshipEmail"
[attr.aria-invalid]="sponsorshipEmailControl.invalid"
/>
</bit-form-field>
</div>
<div class="tw-col-span-7">
<button bitButton bitFormButton buttonType="primary" type="submit">
{{ "redeem" | i18n }}
</button>
</div>
</div>
</form>
<ng-container *ngIf="anyActiveSponsorships$ | async">
<div class="border-bottom">
<table class="table table-hover table-list">
<thead>
<tr>
<th>{{ "recipient" | i18n }}</th>
<th>{{ "sponsoringOrg" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
<th></th>
<bit-table>
<ng-container header>
<tr>
<th bitCell>{{ "recipient" | i18n }}</th>
<th bitCell>{{ "sponsoringOrg" | i18n }}</th>
<th bitCell>{{ "status" | i18n }}</th>
<th bitCell></th>
</tr>
</ng-container>
<ng-template body alignContent="middle">
<ng-container *ngFor="let o of activeSponsorshipOrgs$ | async">
<tr
bitRow
sponsoring-org-row
[sponsoringOrg]="o"
[isSelfHosted]="isSelfHosted"
(sponsorshipRemoved)="forceReload()"
>
<hr />
</tr>
</thead>
<tbody>
<ng-container *ngFor="let o of activeSponsorshipOrgs$ | async">
<tr
sponsoring-org-row
[sponsoringOrg]="o"
[isSelfHosted]="isSelfHosted"
(sponsorshipRemoved)="forceReload()"
></tr>
</ng-container>
</tbody>
</table>
</div>
<small>{{ "sponsoredFamiliesLeaveCopy" | i18n }}</small>
</ng-container>
</ng-template>
</bit-table>
<hr />
<p bitTypography="body2">{{ "sponsoredFamiliesLeaveCopy" | i18n }}</p>
</ng-container>
</ng-container>
</bit-container>

View File

@ -1,8 +1,15 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
import {
FormBuilder,
FormControl,
FormGroup,
Validators,
AbstractControl,
AsyncValidatorFn,
ValidationErrors,
} from "@angular/forms";
import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs";
import { notAllowedValueAsync } from "@bitwarden/angular/admin-console/validators/not-allowed-value-async.validator";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@ -50,9 +57,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
validators: [Validators.required],
}),
sponsorshipEmail: new FormControl("", {
validators: [Validators.email],
validators: [Validators.email, Validators.required],
asyncValidators: [
notAllowedValueAsync(
this.notAllowedValueAsync(
() => firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email))),
true,
),
@ -84,6 +91,15 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
this.loading = false;
this.sponsorshipForm
.get("sponsorshipEmail")
.valueChanges.pipe(takeUntil(this._destroy))
.subscribe((val) => {
if (this.sponsorshipEmailControl.hasError("email")) {
this.sponsorshipEmailControl.setErrors([{ message: this.i18nService.t("invalidEmail") }]);
}
});
}
ngOnDestroy(): void {
@ -91,7 +107,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
this._destroy.complete();
}
async submit() {
submit = async () => {
this.formPromise = this.apiService.postCreateSponsorship(
this.sponsorshipForm.value.selectedSponsorshipOrgId,
{
@ -108,7 +124,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.resetForm();
await this.forceReload();
}
};
async forceReload() {
this.loading = true;
@ -127,4 +143,26 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
get isSelfHosted(): boolean {
return this.platformUtilsService.isSelfHost();
}
notAllowedValueAsync(
valueGetter: () => Promise<string>,
caseInsensitive = false,
): AsyncValidatorFn {
return async (control: AbstractControl): Promise<ValidationErrors | null> => {
let notAllowedValue = await valueGetter();
let controlValue = control.value;
if (caseInsensitive) {
notAllowedValue = notAllowedValue.toLowerCase();
controlValue = controlValue.toLowerCase();
}
if (controlValue === notAllowedValue) {
return {
errors: {
message: this.i18nService.t("cannotSponsorSelf"),
},
};
}
};
}
}