mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-18 02:41:15 +02:00
Merge branch 'main' into autofill/pm-6426-create-alarms-manager-and-update-usage-of-long-lived-timeouts-rework
This commit is contained in:
commit
cd80700c19
@ -3,7 +3,6 @@ import { FormBuilder } from "@angular/forms";
|
|||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
|
||||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
|
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
|
||||||
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
|
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
@ -37,8 +36,6 @@ const BroadcasterSubscriptionId = "LoginComponent";
|
|||||||
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
||||||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||||
environmentModal: ViewContainerRef;
|
environmentModal: ViewContainerRef;
|
||||||
@ViewChild("environmentSelector", { read: ViewContainerRef, static: true })
|
|
||||||
environmentSelector: EnvironmentSelectorComponent;
|
|
||||||
|
|
||||||
protected componentDestroyed$: Subject<void> = new Subject();
|
protected componentDestroyed$: Subject<void> = new Subject();
|
||||||
webVaultHostname = "";
|
webVaultHostname = "";
|
||||||
|
@ -26,6 +26,10 @@ export class OrganizationUserView {
|
|||||||
twoFactorEnabled: boolean;
|
twoFactorEnabled: boolean;
|
||||||
usesKeyConnector: boolean;
|
usesKeyConnector: boolean;
|
||||||
hasMasterPassword: boolean;
|
hasMasterPassword: boolean;
|
||||||
|
/**
|
||||||
|
* True if this organizaztion user has been granted access to Secrets Manager, false otherwise.
|
||||||
|
*/
|
||||||
|
accessSecretsManager: boolean;
|
||||||
|
|
||||||
collections: CollectionAccessSelectionView[] = [];
|
collections: CollectionAccessSelectionView[] = [];
|
||||||
groups: string[] = [];
|
groups: string[] = [];
|
||||||
|
@ -571,7 +571,8 @@ export class PeopleComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
async bulkEnableSM() {
|
async bulkEnableSM() {
|
||||||
const users = this.getCheckedUsers();
|
const users = this.getCheckedUsers().filter((ou) => !ou.accessSecretsManager);
|
||||||
|
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
@ -588,6 +589,7 @@ export class PeopleComponent
|
|||||||
|
|
||||||
await lastValueFrom(dialogRef.closed);
|
await lastValueFrom(dialogRef.closed);
|
||||||
this.selectAll(false);
|
this.selectAll(false);
|
||||||
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
async events(user: OrganizationUserView) {
|
async events(user: OrganizationUserView) {
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
import { ActivatedRoute } from "@angular/router";
|
|
||||||
import { map, Observable, switchMap } from "rxjs";
|
|
||||||
|
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: "organization-billing-tab.component.html",
|
|
||||||
})
|
|
||||||
export class OrganizationBillingTabComponent implements OnInit {
|
|
||||||
showPaymentAndHistory$: Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private organizationService: OrganizationService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.showPaymentAndHistory$ = this.route.params.pipe(
|
|
||||||
switchMap((params) => this.organizationService.get$(params.organizationId)),
|
|
||||||
map(
|
|
||||||
(org) =>
|
|
||||||
!this.platformUtilsService.isSelfHost() &&
|
|
||||||
org.canViewBillingHistory &&
|
|
||||||
org.canEditPaymentMethods,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscr
|
|||||||
import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component";
|
import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component";
|
||||||
import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component";
|
import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component";
|
||||||
import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
|
import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
|
||||||
|
import { SubscriptionStatusComponent } from "./subscription-status.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -38,6 +39,7 @@ import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
|
|||||||
SecretsManagerAdjustSubscriptionComponent,
|
SecretsManagerAdjustSubscriptionComponent,
|
||||||
SecretsManagerSubscribeStandaloneComponent,
|
SecretsManagerSubscribeStandaloneComponent,
|
||||||
SubscriptionHiddenComponent,
|
SubscriptionHiddenComponent,
|
||||||
|
SubscriptionStatusComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class OrganizationBillingModule {}
|
export class OrganizationBillingModule {}
|
||||||
|
@ -12,51 +12,58 @@
|
|||||||
></app-org-subscription-hidden>
|
></app-org-subscription-hidden>
|
||||||
|
|
||||||
<ng-container *ngIf="sub && firstLoaded">
|
<ng-container *ngIf="sub && firstLoaded">
|
||||||
<bit-callout
|
<ng-container *ngIf="!(showUpdatedSubscriptionStatusSection$ | async)">
|
||||||
type="warning"
|
<bit-callout
|
||||||
title="{{ 'canceled' | i18n }}"
|
type="warning"
|
||||||
*ngIf="subscription && subscription.cancelled"
|
title="{{ 'canceled' | i18n }}"
|
||||||
>
|
*ngIf="subscription && subscription.cancelled"
|
||||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
|
||||||
>
|
|
||||||
<bit-callout
|
|
||||||
type="warning"
|
|
||||||
title="{{ 'pendingCancellation' | i18n }}"
|
|
||||||
*ngIf="subscriptionMarkedForCancel"
|
|
||||||
>
|
|
||||||
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
|
|
||||||
<button
|
|
||||||
*ngIf="userOrg.canEditSubscription"
|
|
||||||
bitButton
|
|
||||||
buttonType="secondary"
|
|
||||||
[bitAction]="reinstate"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
{{ "reinstateSubscription" | i18n }}
|
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
||||||
</button>
|
>
|
||||||
</bit-callout>
|
<bit-callout
|
||||||
|
type="warning"
|
||||||
|
title="{{ 'pendingCancellation' | i18n }}"
|
||||||
|
*ngIf="subscriptionMarkedForCancel"
|
||||||
|
>
|
||||||
|
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
|
||||||
|
<button
|
||||||
|
*ngIf="userOrg.canEditSubscription"
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
[bitAction]="reinstate"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{{ "reinstateSubscription" | i18n }}
|
||||||
|
</button>
|
||||||
|
</bit-callout>
|
||||||
|
|
||||||
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||||
<dd>{{ sub.plan.name }}</dd>
|
<dd>{{ sub.plan.name }}</dd>
|
||||||
<ng-container *ngIf="subscription">
|
<ng-container *ngIf="subscription">
|
||||||
<dt>{{ "status" | i18n }}</dt>
|
<dt>{{ "status" | i18n }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span class="tw-capitalize">{{
|
<span class="tw-capitalize">{{
|
||||||
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
|
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
|
||||||
}}</span>
|
}}</span>
|
||||||
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
|
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||||
"pendingCancellation" | i18n
|
"pendingCancellation" | i18n
|
||||||
}}</span>
|
}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||||
{{ "subscriptionExpiration" | i18n }}
|
{{ "subscriptionExpiration" | i18n }}
|
||||||
</dt>
|
</dt>
|
||||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||||
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
||||||
</dd>
|
</dd>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</dl>
|
</dl>
|
||||||
|
</ng-container>
|
||||||
|
<app-subscription-status
|
||||||
|
*ngIf="showUpdatedSubscriptionStatusSection$ | async"
|
||||||
|
[organizationSubscriptionResponse]="sub"
|
||||||
|
(reinstatementRequested)="reinstate()"
|
||||||
|
></app-subscription-status>
|
||||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||||
<div class="tw-flex-col">
|
<div class="tw-flex-col">
|
||||||
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{
|
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs";
|
import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
@ -11,6 +11,8 @@ import { PlanType } from "@bitwarden/common/billing/enums";
|
|||||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||||
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
|
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
|
||||||
import { ProductType } from "@bitwarden/common/enums";
|
import { ProductType } from "@bitwarden/common/enums";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@ -41,6 +43,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
|||||||
showSecretsManagerSubscribe = false;
|
showSecretsManagerSubscribe = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
locale: string;
|
||||||
|
showUpdatedSubscriptionStatusSection$: Observable<boolean>;
|
||||||
|
|
||||||
protected readonly teamsStarter = ProductType.TeamsStarter;
|
protected readonly teamsStarter = ProductType.TeamsStarter;
|
||||||
|
|
||||||
@ -55,6 +59,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
|||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@ -74,6 +79,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
|||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
|
this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$(
|
||||||
|
FeatureFlag.AC1795_UpdatedSubscriptionStatusSection,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@ -86,6 +96,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.locale = await firstValueFrom(this.i18nService.locale$);
|
||||||
this.userOrg = await this.organizationService.get(this.organizationId);
|
this.userOrg = await this.organizationService.get(this.organizationId);
|
||||||
if (this.userOrg.canViewSubscription) {
|
if (this.userOrg.canViewSubscription) {
|
||||||
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
|
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<ng-container>
|
||||||
|
<bit-callout *ngIf="data.callout" [type]="data.callout.severity" [title]="data.callout.header">
|
||||||
|
<p>{{ data.callout.body }}</p>
|
||||||
|
<button
|
||||||
|
*ngIf="data.callout.showReinstatementButton"
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
[bitAction]="requestReinstatement"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{{ "reinstateSubscription" | i18n }}
|
||||||
|
</button>
|
||||||
|
</bit-callout>
|
||||||
|
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||||
|
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||||
|
<dd>{{ planName }}</dd>
|
||||||
|
<ng-container>
|
||||||
|
<dt>{{ data.status.label }}</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="tw-capitalize">
|
||||||
|
{{ displayedStatus }}
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
{{ data.date.label | titlecase }}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ data.date.value | date: "mediumDate" }}
|
||||||
|
</dd>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
</ng-container>
|
@ -0,0 +1,184 @@
|
|||||||
|
import { DatePipe } from "@angular/common";
|
||||||
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
|
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
type ComponentData = {
|
||||||
|
status: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
date: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
callout?: {
|
||||||
|
severity: "danger" | "warning";
|
||||||
|
header: string;
|
||||||
|
body: string;
|
||||||
|
showReinstatementButton: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-subscription-status",
|
||||||
|
templateUrl: "subscription-status.component.html",
|
||||||
|
})
|
||||||
|
export class SubscriptionStatusComponent {
|
||||||
|
@Input({ required: true }) organizationSubscriptionResponse: OrganizationSubscriptionResponse;
|
||||||
|
@Output() reinstatementRequested = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private datePipe: DatePipe,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get displayedStatus(): string {
|
||||||
|
const sponsored = this.subscription.items.some((item) => item.sponsoredSubscriptionItem);
|
||||||
|
return sponsored ? this.i18nService.t("sponsored") : this.data.status.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get planName() {
|
||||||
|
return this.organizationSubscriptionResponse.plan.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status(): string {
|
||||||
|
return this.subscription.status != "canceled" && this.subscription.cancelAtEndDate
|
||||||
|
? "pending_cancellation"
|
||||||
|
: this.subscription.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get subscription() {
|
||||||
|
return this.organizationSubscriptionResponse.subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
get data(): ComponentData {
|
||||||
|
const defaultStatusLabel = this.i18nService.t("status");
|
||||||
|
|
||||||
|
const nextChargeDateLabel = this.i18nService.t("nextCharge");
|
||||||
|
const subscriptionExpiredDateLabel = this.i18nService.t("subscriptionExpired");
|
||||||
|
const cancellationDateLabel = this.i18nService.t("cancellationDate");
|
||||||
|
|
||||||
|
switch (this.status) {
|
||||||
|
case "trialing": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("trial"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: nextChargeDateLabel,
|
||||||
|
value: this.subscription.periodEndDate,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "active": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("active"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: nextChargeDateLabel,
|
||||||
|
value: this.subscription.periodEndDate,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "past_due": {
|
||||||
|
const pastDueText = this.i18nService.t("pastDue");
|
||||||
|
const suspensionDate = this.datePipe.transform(
|
||||||
|
this.subscription.suspensionDate,
|
||||||
|
"mediumDate",
|
||||||
|
);
|
||||||
|
const calloutBody =
|
||||||
|
this.subscription.collectionMethod === "charge_automatically"
|
||||||
|
? this.i18nService.t(
|
||||||
|
"pastDueWarningForChargeAutomatically",
|
||||||
|
this.subscription.gracePeriod,
|
||||||
|
suspensionDate,
|
||||||
|
)
|
||||||
|
: this.i18nService.t(
|
||||||
|
"pastDueWarningForSendInvoice",
|
||||||
|
this.subscription.gracePeriod,
|
||||||
|
suspensionDate,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: pastDueText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: subscriptionExpiredDateLabel,
|
||||||
|
value: this.subscription.unpaidPeriodEndDate,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "warning",
|
||||||
|
header: pastDueText,
|
||||||
|
body: calloutBody,
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "unpaid": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("unpaid"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: subscriptionExpiredDateLabel,
|
||||||
|
value: this.subscription.unpaidPeriodEndDate,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "danger",
|
||||||
|
header: this.i18nService.t("unpaidInvoice"),
|
||||||
|
body: this.i18nService.t("toReactivateYourSubscription"),
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "pending_cancellation": {
|
||||||
|
const pendingCancellationText = this.i18nService.t("pendingCancellation");
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: pendingCancellationText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: cancellationDateLabel,
|
||||||
|
value: this.subscription.periodEndDate,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "warning",
|
||||||
|
header: pendingCancellationText,
|
||||||
|
body: this.i18nService.t("subscriptionPendingCanceled"),
|
||||||
|
showReinstatementButton: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "incomplete_expired":
|
||||||
|
case "canceled": {
|
||||||
|
const canceledText = this.i18nService.t("canceled");
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: canceledText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: cancellationDateLabel,
|
||||||
|
value: this.subscription.periodEndDate,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "danger",
|
||||||
|
header: canceledText,
|
||||||
|
body: this.i18nService.t("subscriptionCanceled"),
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestReinstatement = () => this.reinstatementRequested.emit();
|
||||||
|
}
|
@ -1,14 +1,31 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { Title } from "@angular/platform-browser";
|
import { Title } from "@angular/platform-browser";
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
|
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
|
||||||
import { filter } from "rxjs";
|
import { filter, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
KeyDefinition,
|
||||||
|
ROUTER_DISK,
|
||||||
|
StateProvider,
|
||||||
|
GlobalState,
|
||||||
|
} from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
|
const DEEP_LINK_REDIRECT_URL = new KeyDefinition(ROUTER_DISK, "deepLinkRedirectUrl", {
|
||||||
|
deserializer: (value: string) => value,
|
||||||
|
});
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouterService {
|
export class RouterService {
|
||||||
|
/**
|
||||||
|
* The string value of the URL the user tried to navigate to while unauthenticated.
|
||||||
|
*
|
||||||
|
* Developed to allow users to deep link even when the navigation gets interrupted
|
||||||
|
* by the authentication process.
|
||||||
|
*/
|
||||||
|
private deepLinkRedirectUrlState: GlobalState<string>;
|
||||||
|
|
||||||
private previousUrl: string = undefined;
|
private previousUrl: string = undefined;
|
||||||
private currentUrl: string = undefined;
|
private currentUrl: string = undefined;
|
||||||
|
|
||||||
@ -16,9 +33,11 @@ export class RouterService {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private titleService: Title,
|
private titleService: Title,
|
||||||
private stateService: StateService,
|
private stateProvider: StateProvider,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
) {
|
) {
|
||||||
|
this.deepLinkRedirectUrlState = this.stateProvider.getGlobal(DEEP_LINK_REDIRECT_URL);
|
||||||
|
|
||||||
this.currentUrl = this.router.url;
|
this.currentUrl = this.router.url;
|
||||||
|
|
||||||
router.events
|
router.events
|
||||||
@ -67,14 +86,14 @@ export class RouterService {
|
|||||||
* @param url URL being saved to the Global State
|
* @param url URL being saved to the Global State
|
||||||
*/
|
*/
|
||||||
async persistLoginRedirectUrl(url: string): Promise<void> {
|
async persistLoginRedirectUrl(url: string): Promise<void> {
|
||||||
await this.stateService.setDeepLinkRedirectUrl(url);
|
await this.deepLinkRedirectUrlState.update(() => url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch and clear persisted LoginRedirectUrl if present in state
|
* Fetch and clear persisted LoginRedirectUrl if present in state
|
||||||
*/
|
*/
|
||||||
async getAndClearLoginRedirectUrl(): Promise<string> | undefined {
|
async getAndClearLoginRedirectUrl(): Promise<string> | undefined {
|
||||||
const persistedPreLoginUrl = await this.stateService.getDeepLinkRedirectUrl();
|
const persistedPreLoginUrl = await firstValueFrom(this.deepLinkRedirectUrlState.state$);
|
||||||
|
|
||||||
if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) {
|
if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) {
|
||||||
await this.persistLoginRedirectUrl(null);
|
await this.persistLoginRedirectUrl(null);
|
||||||
|
@ -41,7 +41,7 @@ export class BulkMoveDialogComponent implements OnInit {
|
|||||||
cipherIds: string[] = [];
|
cipherIds: string[] = [];
|
||||||
|
|
||||||
formGroup = this.formBuilder.group({
|
formGroup = this.formBuilder.group({
|
||||||
folderId: ["", [Validators.required]],
|
folderId: ["", [Validators.nullValidator]],
|
||||||
});
|
});
|
||||||
folders$: Observable<FolderView[]>;
|
folders$: Observable<FolderView[]>;
|
||||||
|
|
||||||
|
@ -7678,5 +7678,57 @@
|
|||||||
},
|
},
|
||||||
"subscriptionUpdateFailed": {
|
"subscriptionUpdateFailed": {
|
||||||
"message": "Subscription update failed"
|
"message": "Subscription update failed"
|
||||||
|
},
|
||||||
|
"trial": {
|
||||||
|
"message": "Trial",
|
||||||
|
"description": "A subscription status label."
|
||||||
|
},
|
||||||
|
"pastDue": {
|
||||||
|
"message": "Past due",
|
||||||
|
"description": "A subscription status label"
|
||||||
|
},
|
||||||
|
"subscriptionExpired": {
|
||||||
|
"message": "Subscription expired",
|
||||||
|
"description": "The date header used when a subscription is past due."
|
||||||
|
},
|
||||||
|
"pastDueWarningForChargeAutomatically": {
|
||||||
|
"message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.",
|
||||||
|
"placeholders": {
|
||||||
|
"days": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "11"
|
||||||
|
},
|
||||||
|
"suspension_date": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "01/10/2024"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "A warning shown to the user when their subscription is past due and they are charged automatically."
|
||||||
|
},
|
||||||
|
"pastDueWarningForSendInvoice": {
|
||||||
|
"message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.",
|
||||||
|
"placeholders": {
|
||||||
|
"days": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "11"
|
||||||
|
},
|
||||||
|
"suspension_date": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "01/10/2024"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "A warning shown to the user when their subscription is past due and they pay via invoice."
|
||||||
|
},
|
||||||
|
"unpaidInvoice": {
|
||||||
|
"message": "Unpaid invoice",
|
||||||
|
"description": "The header of a warning box shown to a user whose subscription is unpaid."
|
||||||
|
},
|
||||||
|
"toReactivateYourSubscription": {
|
||||||
|
"message": "To reactivate your subscription, please resolve the past due invoices.",
|
||||||
|
"description": "The body of a warning box shown to a user whose subscription is unpaid."
|
||||||
|
},
|
||||||
|
"cancellationDate": {
|
||||||
|
"message": "Cancellation date",
|
||||||
|
"description": "The date header used when a subscription is cancelled."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,79 +1,81 @@
|
|||||||
<div class="environment-selector-btn">
|
<ng-container
|
||||||
{{ "loggingInOn" | i18n }}:
|
*ngIf="{
|
||||||
<button
|
selectedRegion: selectedRegion$ | async
|
||||||
type="button"
|
} as data"
|
||||||
(click)="toggle(null)"
|
|
||||||
cdkOverlayOrigin
|
|
||||||
#trigger="cdkOverlayOrigin"
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-controls="cdk-overlay-container"
|
|
||||||
>
|
|
||||||
<span class="text-primary">
|
|
||||||
<ng-container *ngIf="selectedRegion$ | async as selectedRegion; else fallback">
|
|
||||||
{{ selectedRegion.domain }}
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #fallback>
|
|
||||||
{{ "selfHostedServer" | i18n }}
|
|
||||||
</ng-template>
|
|
||||||
</span>
|
|
||||||
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template
|
|
||||||
cdkConnectedOverlay
|
|
||||||
[cdkConnectedOverlayOrigin]="trigger"
|
|
||||||
[cdkConnectedOverlayOpen]="isOpen"
|
|
||||||
[cdkConnectedOverlayPositions]="overlayPosition"
|
|
||||||
[cdkConnectedOverlayHasBackdrop]="true"
|
|
||||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
|
||||||
(backdropClick)="isOpen = false"
|
|
||||||
(detach)="close()"
|
|
||||||
>
|
>
|
||||||
<div class="box-content">
|
<div class="environment-selector-btn">
|
||||||
<div
|
{{ "loggingInOn" | i18n }}:
|
||||||
class="environment-selector-dialog"
|
<button
|
||||||
[@transformPanel]="'open'"
|
type="button"
|
||||||
cdkTrapFocus
|
(click)="toggle(null)"
|
||||||
cdkTrapFocusAutoCapture
|
cdkOverlayOrigin
|
||||||
role="dialog"
|
#trigger="cdkOverlayOrigin"
|
||||||
aria-modal="true"
|
aria-haspopup="dialog"
|
||||||
|
aria-controls="cdk-overlay-container"
|
||||||
>
|
>
|
||||||
<ng-container *ngFor="let region of availableRegions">
|
<span class="text-primary">
|
||||||
|
<ng-container *ngIf="data.selectedRegion; else fallback">
|
||||||
|
{{ data.selectedRegion.domain }}
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #fallback>
|
||||||
|
{{ "selfHostedServer" | i18n }}
|
||||||
|
</ng-template>
|
||||||
|
</span>
|
||||||
|
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template
|
||||||
|
cdkConnectedOverlay
|
||||||
|
[cdkConnectedOverlayOrigin]="trigger"
|
||||||
|
[cdkConnectedOverlayOpen]="isOpen"
|
||||||
|
[cdkConnectedOverlayPositions]="overlayPosition"
|
||||||
|
[cdkConnectedOverlayHasBackdrop]="true"
|
||||||
|
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||||
|
(backdropClick)="isOpen = false"
|
||||||
|
(detach)="close()"
|
||||||
|
>
|
||||||
|
<div class="box-content">
|
||||||
|
<div
|
||||||
|
class="environment-selector-dialog"
|
||||||
|
[@transformPanel]="'open'"
|
||||||
|
cdkTrapFocus
|
||||||
|
cdkTrapFocusAutoCapture
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<ng-container *ngFor="let region of availableRegions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="environment-selector-dialog-item"
|
||||||
|
(click)="toggle(region.key)"
|
||||||
|
[attr.aria-pressed]="data.selectedRegion === region ? 'true' : 'false'"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check"
|
||||||
|
style="padding-bottom: 1px"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="data.selectedRegion === region ? 'visible' : 'hidden'"
|
||||||
|
></i>
|
||||||
|
<span>{{ region.domain }}</span>
|
||||||
|
</button>
|
||||||
|
<br />
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="environment-selector-dialog-item"
|
class="environment-selector-dialog-item"
|
||||||
(click)="toggle(region.key)"
|
(click)="toggle(ServerEnvironmentType.SelfHosted)"
|
||||||
[attr.aria-pressed]="selectedEnvironment === region.key ? 'true' : 'false'"
|
[attr.aria-pressed]="data.selectedRegion ? 'false' : 'true'"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-fw bwi-sm bwi-check"
|
class="bwi bwi-fw bwi-sm bwi-check"
|
||||||
style="padding-bottom: 1px"
|
style="padding-bottom: 1px"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
[style.visibility]="selectedEnvironment === region.key ? 'visible' : 'hidden'"
|
[style.visibility]="data.selectedRegion ? 'hidden' : 'visible'"
|
||||||
></i>
|
></i>
|
||||||
<span>{{ region.domain }}</span>
|
<span>{{ "selfHostedServer" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
<br />
|
</div>
|
||||||
</ng-container>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="environment-selector-dialog-item"
|
|
||||||
(click)="toggle(ServerEnvironmentType.SelfHosted)"
|
|
||||||
[attr.aria-pressed]="
|
|
||||||
selectedEnvironment === ServerEnvironmentType.SelfHosted ? 'true' : 'false'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-fw bwi-sm bwi-check"
|
|
||||||
style="padding-bottom: 1px"
|
|
||||||
aria-hidden="true"
|
|
||||||
[style.visibility]="
|
|
||||||
selectedEnvironment === ServerEnvironmentType.SelfHosted ? 'visible' : 'hidden'
|
|
||||||
"
|
|
||||||
></i>
|
|
||||||
<span>{{ "selfHostedServer" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-container>
|
||||||
|
@ -36,11 +36,9 @@ import {
|
|||||||
})
|
})
|
||||||
export class EnvironmentSelectorComponent {
|
export class EnvironmentSelectorComponent {
|
||||||
@Output() onOpenSelfHostedSettings = new EventEmitter();
|
@Output() onOpenSelfHostedSettings = new EventEmitter();
|
||||||
isOpen = false;
|
protected isOpen = false;
|
||||||
showingModal = false;
|
protected ServerEnvironmentType = Region;
|
||||||
selectedEnvironment: Region;
|
protected overlayPosition: ConnectedPosition[] = [
|
||||||
ServerEnvironmentType = Region;
|
|
||||||
overlayPosition: ConnectedPosition[] = [
|
|
||||||
{
|
{
|
||||||
originX: "start",
|
originX: "start",
|
||||||
originY: "bottom",
|
originY: "bottom",
|
||||||
|
@ -36,6 +36,10 @@ export class BillingSubscriptionResponse extends BaseResponse {
|
|||||||
status: string;
|
status: string;
|
||||||
cancelled: boolean;
|
cancelled: boolean;
|
||||||
items: BillingSubscriptionItemResponse[] = [];
|
items: BillingSubscriptionItemResponse[] = [];
|
||||||
|
collectionMethod: string;
|
||||||
|
suspensionDate?: string;
|
||||||
|
unpaidPeriodEndDate?: string;
|
||||||
|
gracePeriod?: number;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
@ -51,6 +55,10 @@ export class BillingSubscriptionResponse extends BaseResponse {
|
|||||||
if (items != null) {
|
if (items != null) {
|
||||||
this.items = items.map((i: any) => new BillingSubscriptionItemResponse(i));
|
this.items = items.map((i: any) => new BillingSubscriptionItemResponse(i));
|
||||||
}
|
}
|
||||||
|
this.collectionMethod = this.getResponseProperty("CollectionMethod");
|
||||||
|
this.suspensionDate = this.getResponseProperty("SuspensionDate");
|
||||||
|
this.unpaidPeriodEndDate = this.getResponseProperty("unpaidPeriodEndDate");
|
||||||
|
this.gracePeriod = this.getResponseProperty("GracePeriod");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ export enum FeatureFlag {
|
|||||||
FlexibleCollectionsMigration = "flexible-collections-migration",
|
FlexibleCollectionsMigration = "flexible-collections-migration",
|
||||||
ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners",
|
ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners",
|
||||||
EnableConsolidatedBilling = "enable-consolidated-billing",
|
EnableConsolidatedBilling = "enable-consolidated-billing",
|
||||||
|
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace this with a type safe lookup of the feature flag values in PM-2282
|
// Replace this with a type safe lookup of the feature flag values in PM-2282
|
||||||
|
@ -243,18 +243,5 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
|
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getApproveLoginRequests: (options?: StorageOptions) => Promise<boolean>;
|
getApproveLoginRequests: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setApproveLoginRequests: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setApproveLoginRequests: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
/**
|
|
||||||
* fetches string value of URL user tried to navigate to while unauthenticated.
|
|
||||||
* @param options Defines the storage options for the URL; Defaults to session Storage.
|
|
||||||
* @returns route called prior to successful login.
|
|
||||||
*/
|
|
||||||
getDeepLinkRedirectUrl: (options?: StorageOptions) => Promise<string>;
|
|
||||||
/**
|
|
||||||
* Store URL in session storage by default, but can be configured. Developed to handle
|
|
||||||
* unauthN interrupted navigation.
|
|
||||||
* @param url URL of route
|
|
||||||
* @param options Defines the storage options for the URL; Defaults to session Storage.
|
|
||||||
*/
|
|
||||||
setDeepLinkRedirectUrl: (url: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
nextUpActiveUser: () => Promise<UserId>;
|
nextUpActiveUser: () => Promise<UserId>;
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,4 @@ export class GlobalState {
|
|||||||
vaultTimeoutAction?: string;
|
vaultTimeoutAction?: string;
|
||||||
enableBrowserIntegration?: boolean;
|
enableBrowserIntegration?: boolean;
|
||||||
enableBrowserIntegrationFingerprint?: boolean;
|
enableBrowserIntegrationFingerprint?: boolean;
|
||||||
deepLinkRedirectUrl?: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1173,23 +1173,6 @@ export class StateService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeepLinkRedirectUrl(options?: StorageOptions): Promise<string> {
|
|
||||||
return (
|
|
||||||
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
|
||||||
)?.deepLinkRedirectUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDeepLinkRedirectUrl(url: string, options?: StorageOptions): Promise<void> {
|
|
||||||
const globals = await this.getGlobals(
|
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
globals.deepLinkRedirectUrl = url;
|
|
||||||
await this.saveGlobals(
|
|
||||||
globals,
|
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
|
protected async getGlobals(options: StorageOptions): Promise<TGlobalState> {
|
||||||
let globals: TGlobalState;
|
let globals: TGlobalState;
|
||||||
if (this.useMemory(options.storageLocation)) {
|
if (this.useMemory(options.storageLocation)) {
|
||||||
|
@ -38,6 +38,7 @@ export const BILLING_DISK = new StateDefinition("billing", "disk");
|
|||||||
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
|
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
|
||||||
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
|
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
|
||||||
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
|
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
|
||||||
|
export const ROUTER_DISK = new StateDefinition("router", "disk");
|
||||||
export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", {
|
export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", {
|
||||||
web: "disk-local",
|
web: "disk-local",
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class="tw-my-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-secondary-500"
|
class="tw-my-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-secondary-500"
|
||||||
role="separator"
|
role="separator"
|
||||||
|
aria-hidden="true"
|
||||||
></div>
|
></div>
|
||||||
|
@ -88,6 +88,7 @@ export class MenuTriggerForDirective implements OnDestroy {
|
|||||||
}
|
}
|
||||||
this.destroyMenu();
|
this.destroyMenu();
|
||||||
});
|
});
|
||||||
|
this.menu.keyManager.setFirstItemActive();
|
||||||
this.keyDownEventsSub =
|
this.keyDownEventsSub =
|
||||||
this.menu.keyManager &&
|
this.menu.keyManager &&
|
||||||
this.overlayRef
|
this.overlayRef
|
||||||
|
Loading…
Reference in New Issue
Block a user