1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-08-27 23:31:41 +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:
Cesar Gonzalez 2024-04-02 16:18:18 -05:00 committed by GitHub
commit cd80700c19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 447 additions and 187 deletions

View File

@ -3,7 +3,6 @@ import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
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 { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@ -37,8 +36,6 @@ const BroadcasterSubscriptionId = "LoginComponent";
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
@ViewChild("environmentSelector", { read: ViewContainerRef, static: true })
environmentSelector: EnvironmentSelectorComponent;
protected componentDestroyed$: Subject<void> = new Subject();
webVaultHostname = "";

View File

@ -26,6 +26,10 @@ export class OrganizationUserView {
twoFactorEnabled: boolean;
usesKeyConnector: boolean;
hasMasterPassword: boolean;
/**
* True if this organizaztion user has been granted access to Secrets Manager, false otherwise.
*/
accessSecretsManager: boolean;
collections: CollectionAccessSelectionView[] = [];
groups: string[] = [];

View File

@ -571,7 +571,8 @@ export class PeopleComponent
}
async bulkEnableSM() {
const users = this.getCheckedUsers();
const users = this.getCheckedUsers().filter((ou) => !ou.accessSecretsManager);
if (users.length === 0) {
this.platformUtilsService.showToast(
"error",
@ -588,6 +589,7 @@ export class PeopleComponent
await lastValueFrom(dialogRef.closed);
this.selectAll(false);
await this.load();
}
async events(user: OrganizationUserView) {

View File

@ -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,
),
);
}
}

View File

@ -17,6 +17,7 @@ import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscr
import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component";
import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component";
import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
import { SubscriptionStatusComponent } from "./subscription-status.component";
@NgModule({
imports: [
@ -38,6 +39,7 @@ import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
SecretsManagerAdjustSubscriptionComponent,
SecretsManagerSubscribeStandaloneComponent,
SubscriptionHiddenComponent,
SubscriptionStatusComponent,
],
})
export class OrganizationBillingModule {}

View File

@ -12,51 +12,58 @@
></app-org-subscription-hidden>
<ng-container *ngIf="sub && firstLoaded">
<bit-callout
type="warning"
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"
<ng-container *ngIf="!(showUpdatedSubscriptionStatusSection$ | async)">
<bit-callout
type="warning"
title="{{ 'canceled' | i18n }}"
*ngIf="subscription && subscription.cancelled"
>
{{ "reinstateSubscription" | i18n }}
</button>
</bit-callout>
{{ "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 }}
</button>
</bit-callout>
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
<dt>{{ "billingPlan" | i18n }}</dt>
<dd>{{ sub.plan.name }}</dd>
<ng-container *ngIf="subscription">
<dt>{{ "status" | i18n }}</dt>
<dd>
<span class="tw-capitalize">{{
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
}}</span>
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
"pendingCancellation" | i18n
}}</span>
</dd>
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
{{ "subscriptionExpiration" | i18n }}
</dt>
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
</dd>
</ng-container>
</dl>
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
<dt>{{ "billingPlan" | i18n }}</dt>
<dd>{{ sub.plan.name }}</dd>
<ng-container *ngIf="subscription">
<dt>{{ "status" | i18n }}</dt>
<dd>
<span class="tw-capitalize">{{
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
}}</span>
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
"pendingCancellation" | i18n
}}</span>
</dd>
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
{{ "subscriptionExpiration" | i18n }}
</dt>
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
</dd>
</ng-container>
</dl>
</ng-container>
<app-subscription-status
*ngIf="showUpdatedSubscriptionStatusSection$ | async"
[organizationSubscriptionResponse]="sub"
(reinstatementRequested)="reinstate()"
></app-subscription-status>
<ng-container *ngIf="userOrg.canEditSubscription">
<div class="tw-flex-col">
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{

View File

@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
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 { 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 { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -41,6 +43,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
showSecretsManagerSubscribe = false;
firstLoaded = false;
loading: boolean;
locale: string;
showUpdatedSubscriptionStatusSection$: Observable<boolean>;
protected readonly teamsStarter = ProductType.TeamsStarter;
@ -55,6 +59,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
private organizationApiService: OrganizationApiServiceAbstraction,
private route: ActivatedRoute,
private dialogService: DialogService,
private configService: ConfigService,
) {}
async ngOnInit() {
@ -74,6 +79,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
takeUntil(this.destroy$),
)
.subscribe();
this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$(
FeatureFlag.AC1795_UpdatedSubscriptionStatusSection,
false,
);
}
ngOnDestroy() {
@ -86,6 +96,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return;
}
this.loading = true;
this.locale = await firstValueFrom(this.i18nService.locale$);
this.userOrg = await this.organizationService.get(this.organizationId);
if (this.userOrg.canViewSubscription) {
this.sub = await this.organizationApiService.getSubscription(this.organizationId);

View File

@ -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>

View File

@ -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();
}

View File

@ -1,14 +1,31 @@
import { Injectable } from "@angular/core";
import { Title } from "@angular/platform-browser";
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 { StateService } from "@bitwarden/common/platform/abstractions/state.service";
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()
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 currentUrl: string = undefined;
@ -16,9 +33,11 @@ export class RouterService {
private router: Router,
private activatedRoute: ActivatedRoute,
private titleService: Title,
private stateService: StateService,
private stateProvider: StateProvider,
i18nService: I18nService,
) {
this.deepLinkRedirectUrlState = this.stateProvider.getGlobal(DEEP_LINK_REDIRECT_URL);
this.currentUrl = this.router.url;
router.events
@ -67,14 +86,14 @@ export class RouterService {
* @param url URL being saved to the Global State
*/
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
*/
async getAndClearLoginRedirectUrl(): Promise<string> | undefined {
const persistedPreLoginUrl = await this.stateService.getDeepLinkRedirectUrl();
const persistedPreLoginUrl = await firstValueFrom(this.deepLinkRedirectUrlState.state$);
if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) {
await this.persistLoginRedirectUrl(null);

View File

@ -41,7 +41,7 @@ export class BulkMoveDialogComponent implements OnInit {
cipherIds: string[] = [];
formGroup = this.formBuilder.group({
folderId: ["", [Validators.required]],
folderId: ["", [Validators.nullValidator]],
});
folders$: Observable<FolderView[]>;

View File

@ -7678,5 +7678,57 @@
},
"subscriptionUpdateFailed": {
"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."
}
}

View File

@ -1,79 +1,81 @@
<div class="environment-selector-btn">
{{ "loggingInOn" | i18n }}:
<button
type="button"
(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()"
<ng-container
*ngIf="{
selectedRegion: selectedRegion$ | async
} as data"
>
<div class="box-content">
<div
class="environment-selector-dialog"
[@transformPanel]="'open'"
cdkTrapFocus
cdkTrapFocusAutoCapture
role="dialog"
aria-modal="true"
<div class="environment-selector-btn">
{{ "loggingInOn" | i18n }}:
<button
type="button"
(click)="toggle(null)"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
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
type="button"
class="environment-selector-dialog-item"
(click)="toggle(region.key)"
[attr.aria-pressed]="selectedEnvironment === region.key ? 'true' : 'false'"
(click)="toggle(ServerEnvironmentType.SelfHosted)"
[attr.aria-pressed]="data.selectedRegion ? 'false' : 'true'"
>
<i
class="bwi bwi-fw bwi-sm bwi-check"
style="padding-bottom: 1px"
aria-hidden="true"
[style.visibility]="selectedEnvironment === region.key ? 'visible' : 'hidden'"
[style.visibility]="data.selectedRegion ? 'hidden' : 'visible'"
></i>
<span>{{ region.domain }}</span>
<span>{{ "selfHostedServer" | i18n }}</span>
</button>
<br />
</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>

View File

@ -36,11 +36,9 @@ import {
})
export class EnvironmentSelectorComponent {
@Output() onOpenSelfHostedSettings = new EventEmitter();
isOpen = false;
showingModal = false;
selectedEnvironment: Region;
ServerEnvironmentType = Region;
overlayPosition: ConnectedPosition[] = [
protected isOpen = false;
protected ServerEnvironmentType = Region;
protected overlayPosition: ConnectedPosition[] = [
{
originX: "start",
originY: "bottom",

View File

@ -36,6 +36,10 @@ export class BillingSubscriptionResponse extends BaseResponse {
status: string;
cancelled: boolean;
items: BillingSubscriptionItemResponse[] = [];
collectionMethod: string;
suspensionDate?: string;
unpaidPeriodEndDate?: string;
gracePeriod?: number;
constructor(response: any) {
super(response);
@ -51,6 +55,10 @@ export class BillingSubscriptionResponse extends BaseResponse {
if (items != null) {
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");
}
}

View File

@ -8,6 +8,7 @@ export enum FeatureFlag {
FlexibleCollectionsMigration = "flexible-collections-migration",
ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners",
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

View File

@ -243,18 +243,5 @@ export abstract class StateService<T extends Account = Account> {
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
getApproveLoginRequests: (options?: StorageOptions) => Promise<boolean>;
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>;
}

View File

@ -4,5 +4,4 @@ export class GlobalState {
vaultTimeoutAction?: string;
enableBrowserIntegration?: boolean;
enableBrowserIntegrationFingerprint?: boolean;
deepLinkRedirectUrl?: string;
}

View File

@ -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> {
let globals: TGlobalState;
if (this.useMemory(options.storageLocation)) {

View File

@ -38,6 +38,7 @@ export const BILLING_DISK = new StateDefinition("billing", "disk");
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
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", {
web: "disk-local",
});

View File

@ -1,4 +1,5 @@
<div
class="tw-my-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-secondary-500"
role="separator"
aria-hidden="true"
></div>

View File

@ -88,6 +88,7 @@ export class MenuTriggerForDirective implements OnDestroy {
}
this.destroyMenu();
});
this.menu.keyManager.setFirstItemActive();
this.keyDownEventsSub =
this.menu.keyManager &&
this.overlayRef