1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-18 02:19:18 +00:00

Merge branch 'main' into uif/cl-349/popover-arrow

This commit is contained in:
Vicki League
2025-02-18 12:24:39 -05:00
committed by GitHub
196 changed files with 6584 additions and 2653 deletions

View File

@@ -129,10 +129,7 @@ export class OrganizationLayoutComponent implements OnInit {
),
);
this.integrationPageEnabled$ = combineLatest(
this.organization$,
this.configService.getFeatureFlag$(FeatureFlag.PM14505AdminConsoleIntegrationPage),
).pipe(map(([org, featureFlagEnabled]) => featureFlagEnabled && org.canAccessIntegrations));
this.integrationPageEnabled$ = this.organization$.pipe(map((org) => org.canAccessIntegrations));
this.domainVerificationNavigationTextKey = (await this.configService.getFeatureFlag(
FeatureFlag.AccountDeprovisioning,

View File

@@ -4,7 +4,6 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
canAccessOrgAdmin,
canAccessGroupsTab,
@@ -14,7 +13,6 @@ import {
canAccessSettingsTab,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard";
import { organizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
@@ -45,7 +43,6 @@ const routes: Routes = [
{
path: "integrations",
canActivate: [
canAccessFeature(FeatureFlag.PM14505AdminConsoleIntegrationPage),
isEnterpriseOrgGuard(false),
organizationPermissionsGuard(canAccessIntegrations),
],

View File

@@ -1043,7 +1043,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
if (this.organization.useSecretsManager) {
request.secretsManager = {
seats: this.sub.smSeats,
additionalMachineAccounts: this.sub.smServiceAccounts,
additionalMachineAccounts:
this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount,
};
}

View File

@@ -55,15 +55,21 @@ export class StripeService {
* Re-mounts previously created Stripe credit card [elements]{@link https://docs.stripe.com/js/elements_object/create} into the HTML elements
* specified during the {@link loadStripe} call. This is useful for when those HTML elements are removed from the DOM by Angular.
*/
mountElements() {
mountElements(i: number = 0) {
setTimeout(() => {
if (!document.querySelector(this.elementIds.cardNumber) && i < 10) {
this.logService.warning("Stripe container missing, retrying...");
this.mountElements(i + 1);
return;
}
const cardNumber = this.elements.getElement("cardNumber");
const cardExpiry = this.elements.getElement("cardExpiry");
const cardCvc = this.elements.getElement("cardCvc");
cardNumber.mount(this.elementIds.cardNumber);
cardExpiry.mount(this.elementIds.cardExpiry);
cardCvc.mount(this.elementIds.cardCvc);
});
}, 50);
}
/**

View File

@@ -12,12 +12,14 @@
<h1
bitTypography="h1"
noMargin
class="tw-m-0 tw-mr-2 tw-truncate tw-leading-10"
class="tw-m-0 tw-mr-2 tw-leading-10 tw-flex"
[title]="title || (routeData.titleId | i18n)"
>
<i *ngIf="icon" class="bwi {{ icon }}" aria-hidden="true"></i>
{{ title || (routeData.titleId | i18n) }}
<ng-content select="[slot=title-suffix]"></ng-content>
<div class="tw-truncate">
<i *ngIf="icon" class="bwi {{ icon }}" aria-hidden="true"></i>
{{ title || (routeData.titleId | i18n) }}
</div>
<div><ng-content select="[slot=title-suffix]"></ng-content></div>
</h1>
</div>
<div class="tw-ml-auto tw-flex tw-flex-col tw-gap-4">

View File

@@ -1,232 +0,0 @@
// import { CommonModule } from "@angular/common";
// import { Component, importProvidersFrom, Injectable, Input } from "@angular/core";
// import { RouterModule } from "@angular/router";
// import {
// applicationConfig,
// componentWrapperDecorator,
// Meta,
// moduleMetadata,
// Story,
// } from "@storybook/angular";
// import { BehaviorSubject, combineLatest, map, of } from "rxjs";
// import { JslibModule } from "@bitwarden/angular/jslib.module";
// import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
// import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
// import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
// import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
// import {
// AvatarModule,
// BreadcrumbsModule,
// ButtonModule,
// IconButtonModule,
// IconModule,
// InputModule,
// MenuModule,
// NavigationModule,
// TabsModule,
// TypographyModule,
// } from "@bitwarden/components";
// import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component";
// import { PreloadedEnglishI18nModule } from "../../core/tests";
// import { WebHeaderComponent } from "../header/web-header.component";
// import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service";
// @Injectable({
// providedIn: "root",
// })
// class MockStateService {
// activeAccount$ = new BehaviorSubject("1").asObservable();
// accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable();
// }
// class MockMessagingService implements MessagingService {
// send(subscriber: string, arg?: any) {
// alert(subscriber);
// }
// }
// class MockVaultTimeoutService {
// availableVaultTimeoutActions$() {
// return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable();
// }
// }
// class MockPlatformUtilsService {
// isSelfHost() {
// return false;
// }
// }
// @Component({
// selector: "product-switcher",
// template: `<button bitIconButton="bwi-filter"></button>`,
// })
// class MockProductSwitcher {}
// @Component({
// selector: "dynamic-avatar",
// template: `<bit-avatar [text]="name$ | async"></bit-avatar>`,
// standalone: true,
// imports: [CommonModule, AvatarModule],
// })
// class MockDynamicAvatar implements Partial<DynamicAvatarComponent> {
// protected name$ = combineLatest([
// this.stateService.accounts$,
// this.stateService.activeAccount$,
// ]).pipe(
// map(
// ([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name,
// ),
// );
// @Input()
// text: string;
// constructor(private stateService: MockStateService) {}
// }
// export default {
// title: "Web/Header",
// component: WebHeaderComponent,
// decorators: [
// componentWrapperDecorator(
// (story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`,
// ),
// moduleMetadata({
// imports: [
// JslibModule,
// AvatarModule,
// BreadcrumbsModule,
// ButtonModule,
// IconButtonModule,
// IconModule,
// InputModule,
// MenuModule,
// TabsModule,
// TypographyModule,
// NavigationModule,
// MockDynamicAvatar,
// ],
// declarations: [WebHeaderComponent, MockProductSwitcher],
// providers: [
// { provide: StateService, useClass: MockStateService },
// {
// provide: WebLayoutMigrationBannerService,
// useValue: {
// showBanner$: of(false),
// } as Partial<WebLayoutMigrationBannerService>,
// },
// { provide: PlatformUtilsService, useClass: MockPlatformUtilsService },
// { provide: VaultTimeoutSettingsService, useClass: MockVaultTimeoutService },
// {
// provide: MessagingService,
// useFactory: () => {
// return new MockMessagingService();
// },
// },
// ],
// }),
// applicationConfig({
// providers: [
// importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
// importProvidersFrom(PreloadedEnglishI18nModule),
// ],
// }),
// ],
// } as Meta;
// export const KitchenSink: Story = (args) => ({
// props: args,
// template: `
// <app-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
// <bit-breadcrumbs slot="breadcrumbs">
// <bit-breadcrumb>Foo</bit-breadcrumb>
// <bit-breadcrumb>Bar</bit-breadcrumb>
// </bit-breadcrumbs>
// <input
// bitInput
// placeholder="Ask Jeeves"
// type="text"
// />
// <button bitButton buttonType="primary">New</button>
// <button bitButton slot="secondary">Click Me 🎉</button>
// <bit-tab-nav-bar slot="tabs">
// <bit-tab-link route="">Foo</bit-tab-link>
// <bit-tab-link route="#bar">Bar</bit-tab-link>
// </bit-tab-nav-bar>
// </app-header>
// `,
// });
// export const Basic: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug"></app-header>
// `,
// });
// export const WithLongTitle: Story = (args) => ({
// props: args,
// template: `
// <app-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug"></app-header>
// `,
// });
// export const WithBreadcrumbs: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
// <bit-breadcrumbs slot="breadcrumbs">
// <bit-breadcrumb>Foo</bit-breadcrumb>
// <bit-breadcrumb>Bar</bit-breadcrumb>
// </bit-breadcrumbs>
// </app-header>
// `,
// });
// export const WithSearch: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
// <input
// bitInput
// placeholder="Ask Jeeves"
// type="text"
// />
// </app-header>
// `,
// });
// export const WithSecondaryContent: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
// <button bitButton slot="secondary">Click Me 🎉</button>
// </app-header>
// `,
// });
// export const WithTabs: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
// <bit-tab-nav-bar slot="tabs">
// <bit-tab-link route="">Foo</bit-tab-link>
// <bit-tab-link route="#bar">Bar</bit-tab-link>
// </bit-tab-nav-bar>
// </app-header>
// `,
// });
// export const WithTitleSuffixComponent: Story = (args) => ({
// props: args,
// template: `
// <app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
// <ng-container slot="title-suffix"><i class="bwi bwi-spinner bwi-spin"></i></ng-container>
// </app-header>
// `,
// });

View File

@@ -0,0 +1,260 @@
import { CommonModule } from "@angular/common";
import { Component, importProvidersFrom, Injectable, Input } from "@angular/core";
import { RouterModule } from "@angular/router";
import {
applicationConfig,
componentWrapperDecorator,
Meta,
moduleMetadata,
StoryObj,
} from "@storybook/angular";
import { BehaviorSubject, combineLatest, map, of } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import {
AvatarModule,
BreadcrumbsModule,
ButtonModule,
IconButtonModule,
IconModule,
InputModule,
MenuModule,
NavigationModule,
TabsModule,
TypographyModule,
} from "@bitwarden/components";
import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component";
import { PreloadedEnglishI18nModule } from "../../core/tests";
import { WebHeaderComponent } from "../header/web-header.component";
import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service";
@Injectable({
providedIn: "root",
})
class MockStateService {
activeAccount$ = new BehaviorSubject("1").asObservable();
accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable();
}
@Component({
selector: "product-switcher",
template: `<button type="button" bitIconButton="bwi-filter"></button>`,
})
class MockProductSwitcher {}
@Component({
selector: "dynamic-avatar",
template: `<bit-avatar [text]="name$ | async"></bit-avatar>`,
standalone: true,
imports: [CommonModule, AvatarModule],
})
class MockDynamicAvatar implements Partial<DynamicAvatarComponent> {
protected name$ = combineLatest([
this.stateService.accounts$,
this.stateService.activeAccount$,
]).pipe(
map(
([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name,
),
);
@Input()
text?: string;
constructor(private stateService: MockStateService) {}
}
export default {
title: "Web/Header",
component: WebHeaderComponent,
decorators: [
componentWrapperDecorator(
(story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`,
),
moduleMetadata({
imports: [
JslibModule,
AvatarModule,
BreadcrumbsModule,
ButtonModule,
IconButtonModule,
IconModule,
InputModule,
MenuModule,
TabsModule,
TypographyModule,
NavigationModule,
MockDynamicAvatar,
],
declarations: [WebHeaderComponent, MockProductSwitcher],
providers: [
{ provide: StateService, useClass: MockStateService },
{
provide: AccountService,
useValue: {
activeAccount$: of({
name: "Foobar Warden",
}),
} as Partial<AccountService>,
},
{
provide: WebLayoutMigrationBannerService,
useValue: {
showBanner$: of(false),
} as Partial<WebLayoutMigrationBannerService>,
},
{
provide: PlatformUtilsService,
useValue: {
isSelfHost() {
return false;
},
} as Partial<PlatformUtilsService>,
},
{
provide: VaultTimeoutSettingsService,
useValue: {
availableVaultTimeoutActions$() {
return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable();
},
} as Partial<VaultTimeoutSettingsService>,
},
{
provide: MessagingService,
useValue: {
send: (...args: any[]) => {
// eslint-disable-next-line no-console
console.log("MessagingService.send", args);
},
} as Partial<MessagingService>,
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
} as Meta;
type Story = StoryObj<WebHeaderComponent>;
export const KitchenSink: Story = {
render: (args) => ({
props: args,
template: `
<app-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
<bit-breadcrumbs slot="breadcrumbs">
<bit-breadcrumb>Foo</bit-breadcrumb>
<bit-breadcrumb>Bar</bit-breadcrumb>
</bit-breadcrumbs>
<input
bitInput
placeholder="Ask Jeeves"
type="text"
/>
<button bitButton buttonType="primary">New</button>
<button bitButton slot="secondary">Click Me 🎉</button>
<bit-tab-nav-bar slot="tabs">
<bit-tab-link route="">Foo</bit-tab-link>
<bit-tab-link route="#bar">Bar</bit-tab-link>
</bit-tab-nav-bar>
</app-header>
`,
}),
};
export const Basic: Story = {
render: (args: any) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug"></app-header>
`,
}),
};
export const WithLongTitle: Story = {
render: (arg: any) => ({
props: arg,
template: `
<app-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
<ng-container slot="title-suffix"><i class="bwi bwi-key"></i></ng-container>
</app-header>
`,
}),
};
export const WithBreadcrumbs: Story = {
render: (args: any) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<bit-breadcrumbs slot="breadcrumbs">
<bit-breadcrumb>Foo</bit-breadcrumb>
<bit-breadcrumb>Bar</bit-breadcrumb>
</bit-breadcrumbs>
</app-header>
`,
}),
};
export const WithSearch: Story = {
render: (args: any) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<input
bitInput
placeholder="Ask Jeeves"
type="text"
/>
</app-header>
`,
}),
};
export const WithSecondaryContent: Story = {
render: (args) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<button bitButton slot="secondary">Click Me 🎉</button>
</app-header>
`,
}),
};
export const WithTabs: Story = {
render: (args) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<bit-tab-nav-bar slot="tabs">
<bit-tab-link route="">Foo</bit-tab-link>
<bit-tab-link route="#bar">Bar</bit-tab-link>
</bit-tab-nav-bar>
</app-header>
`,
}),
};
export const WithTitleSuffixComponent: Story = {
render: (args) => ({
props: args,
template: `
<app-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<ng-container slot="title-suffix"><i class="bwi bwi-spinner bwi-spin"></i></ng-container>
</app-header>
`,
}),
};

View File

@@ -1,10 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Observable } from "rxjs";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultOnboardingTasks } from "../vault-onboarding.service";
export abstract class VaultOnboardingService {
vaultOnboardingState$: Observable<VaultOnboardingTasks>;
abstract setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise<void>;
abstract setVaultOnboardingTasks(userId: UserId, newState: VaultOnboardingTasks): Promise<void>;
abstract vaultOnboardingState$(userId: UserId): Observable<VaultOnboardingTasks | null>;
}

View File

@@ -1,14 +1,13 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import {
ActiveUserState,
SingleUserState,
StateProvider,
UserKeyDefinition,
VAULT_ONBOARDING,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./abstraction/vault-onboarding.service";
@@ -26,20 +25,20 @@ const VAULT_ONBOARDING_KEY = new UserKeyDefinition<VaultOnboardingTasks>(
clearOn: [], // do not clear tutorials
},
);
@Injectable()
export class VaultOnboardingService implements VaultOnboardingServiceAbstraction {
private vaultOnboardingState: ActiveUserState<VaultOnboardingTasks>;
vaultOnboardingState$: Observable<VaultOnboardingTasks>;
constructor(private stateProvider: StateProvider) {}
constructor(private stateProvider: StateProvider) {
this.vaultOnboardingState = this.stateProvider.getActive(VAULT_ONBOARDING_KEY);
this.vaultOnboardingState$ = this.vaultOnboardingState.state$;
private vaultOnboardingState(userId: UserId): SingleUserState<VaultOnboardingTasks> {
return this.stateProvider.getUser(userId, VAULT_ONBOARDING_KEY);
}
async setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise<void> {
await this.vaultOnboardingState.update(() => {
return { ...newState };
});
vaultOnboardingState$(userId: UserId): Observable<VaultOnboardingTasks | null> {
return this.vaultOnboardingState(userId).state$;
}
async setVaultOnboardingTasks(userId: UserId, newState: VaultOnboardingTasks): Promise<void> {
const state = this.vaultOnboardingState(userId);
await state.update(() => ({ ...newState }));
}
}

View File

@@ -7,9 +7,14 @@ import { Subject, of } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { StateProvider } from "@bitwarden/common/platform/state";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum";
@@ -24,9 +29,11 @@ describe("VaultOnboardingComponent", () => {
let mockPolicyService: MockProxy<PolicyService>;
let mockI18nService: MockProxy<I18nService>;
let mockVaultOnboardingService: MockProxy<VaultOnboardingServiceAbstraction>;
let mockStateProvider: Partial<StateProvider>;
let setInstallExtLinkSpy: any;
let individualVaultPolicyCheckSpy: any;
let mockConfigService: MockProxy<ConfigService>;
const mockAccountService: FakeAccountService = mockAccountServiceWith(Utils.newGuid() as UserId);
let mockStateProvider: Partial<StateProvider>;
beforeEach(() => {
mockPolicyService = mock<PolicyService>();
@@ -36,6 +43,7 @@ describe("VaultOnboardingComponent", () => {
getProfile: jest.fn(),
};
mockVaultOnboardingService = mock<VaultOnboardingServiceAbstraction>();
mockConfigService = mock<ConfigService>();
mockStateProvider = {
getActive: jest.fn().mockReturnValue(
of({
@@ -56,6 +64,8 @@ describe("VaultOnboardingComponent", () => {
{ provide: VaultOnboardingServiceAbstraction, useValue: mockVaultOnboardingService },
{ provide: I18nService, useValue: mockI18nService },
{ provide: ApiService, useValue: mockApiService },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: AccountService, useValue: mockAccountService },
{ provide: StateProvider, useValue: mockStateProvider },
],
}).compileComponents();
@@ -67,11 +77,15 @@ describe("VaultOnboardingComponent", () => {
.mockReturnValue(undefined);
jest.spyOn(component, "checkCreationDate").mockReturnValue(null);
jest.spyOn(window, "postMessage").mockImplementation(jest.fn());
(component as any).vaultOnboardingService.vaultOnboardingState$ = of({
createAccount: true,
importData: false,
installExtension: false,
});
(component as any).vaultOnboardingService.vaultOnboardingState$ = jest
.fn()
.mockImplementation(() => {
return of({
createAccount: true,
importData: false,
installExtension: false,
});
});
});
it("should create", () => {
@@ -165,12 +179,15 @@ describe("VaultOnboardingComponent", () => {
.spyOn((component as any).vaultOnboardingService, "setVaultOnboardingTasks")
.mockReturnValue(Promise.resolve());
(component as any).vaultOnboardingService.vaultOnboardingState$ = of({
createAccount: true,
importData: false,
installExtension: false,
});
(component as any).vaultOnboardingService.vaultOnboardingState$ = jest
.fn()
.mockImplementation(() => {
return of({
createAccount: true,
importData: false,
installExtension: false,
});
});
const eventData = { data: { command: VaultOnboardingMessages.HasBwInstalled } };
(component as any).showOnboarding = true;

View File

@@ -18,7 +18,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -57,15 +61,20 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
protected onboardingTasks$: Observable<VaultOnboardingTasks>;
protected showOnboarding = false;
private activeId: UserId;
constructor(
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
private apiService: ApiService,
private vaultOnboardingService: VaultOnboardingServiceAbstraction,
private configService: ConfigService,
private accountService: AccountService,
) {}
async ngOnInit() {
this.onboardingTasks$ = this.vaultOnboardingService.vaultOnboardingState$;
this.activeId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.onboardingTasks$ = this.vaultOnboardingService.vaultOnboardingState$(this.activeId);
await this.setOnboardingTasks();
this.setInstallExtLink();
this.individualVaultPolicyCheck();
@@ -80,7 +89,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
importData: this.ciphers.length > 0,
installExtension: currentTasks.installExtension,
};
await this.vaultOnboardingService.setVaultOnboardingTasks(updatedTasks);
await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, updatedTasks);
}
}
@@ -109,7 +118,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
importData: currentTasks.importData,
installExtension: true,
};
await this.vaultOnboardingService.setVaultOnboardingTasks(updatedTasks);
await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, updatedTasks);
}
}
@@ -152,7 +161,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
private async saveCompletedTasks(vaultTasks: VaultOnboardingTasks) {
this.showOnboarding = Object.values(vaultTasks).includes(false);
await this.vaultOnboardingService.setVaultOnboardingTasks(vaultTasks);
await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, vaultTasks);
}
individualVaultPolicyCheck() {

View File

@@ -104,8 +104,8 @@
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization"
class="tw-shrink-0"
>
<!-- "New" menu is always shown unless the user cannot create a cipher -->
<ng-container *ngIf="canCreateCipher">
<!-- "New" menu is always shown unless the user cannot create a cipher and cannot create a collection-->
<ng-container *ngIf="canCreateCipher || canCreateCollection">
<div appListDropdown>
<button
bitButton
@@ -119,24 +119,26 @@
{{ "new" | i18n }}<i class="bwi tw-ml-2" aria-hidden="true"></i>
</button>
<bit-menu #addOptions aria-labelledby="newItemDropdown">
<button type="button" bitMenuItem (click)="addCipher(CipherType.Login)">
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
{{ "typeLogin" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.Card)">
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
{{ "typeCard" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.Identity)">
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
{{ "typeIdentity" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.SecureNote)">
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</button>
<ng-container *ngIf="canCreateCipher">
<button type="button" bitMenuItem (click)="addCipher(CipherType.Login)">
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
{{ "typeLogin" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.Card)">
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
{{ "typeCard" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.Identity)">
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
{{ "typeIdentity" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCipher(CipherType.SecureNote)">
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="canCreateCollection">
<bit-menu-divider></bit-menu-divider>
<bit-menu-divider *ngIf="canCreateCipher"></bit-menu-divider>
<button type="button" bitMenuItem (click)="addCollection()">
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
{{ "collection" | i18n }}

File diff suppressed because it is too large Load Diff

View File

@@ -465,10 +465,10 @@
"message": "Edita la carpeta"
},
"newFolder": {
"message": "New folder"
"message": "Carpeta nova"
},
"folderName": {
"message": "Folder name"
"message": "Nom de la carpeta"
},
"folderHintText": {
"message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums"
@@ -2210,7 +2210,7 @@
"message": "View items, hidden passwords"
},
"editItems": {
"message": "Edit items"
"message": "Edita elements"
},
"editItemsHidePass": {
"message": "Edit items, hidden passwords"

View File

@@ -6,7 +6,7 @@
"message": "Aplicaciones críticas"
},
"accessIntelligence": {
"message": "Access Intelligence"
"message": "Inteligencia de Acceso"
},
"riskInsights": {
"message": "Risk Insights"

View File

@@ -235,7 +235,7 @@
"message": "Povijest stavke"
},
"authenticatorKey": {
"message": "Kôd za provjeru"
"message": "Ključ autentifikatora"
},
"autofillOptions": {
"message": "Postavke auto-ispune"
@@ -405,7 +405,7 @@
"message": "Bitwarden može pohraniti i ispuniti kodove za dvostruku autentifikaciju. Kopiraj i zalijepi ključ u ovo polje."
},
"totpHelperWithCapture": {
"message": "Bitwarden može pohraniti i ispuniti kodove za dvostruku autentifikaciju. Odaberi ikonu kamere i označi QR kôd za provjeru autentičnosti ove web stranice ili kopiraj i zalijepi ključ u ovo polje."
"message": "Bitwarden može pohraniti i ispuniti kodove provjeru dvostruke autentifikacije. Odaberi ikonu kamere i označi QR kôd za provjeru autentičnosti ove web stranice ili kopiraj i zalijepi ključ u ovo polje."
},
"learnMoreAboutAuthenticators": {
"message": "Više o autentifikatorima"
@@ -2255,7 +2255,7 @@
"message": "Nastavi na bitwarden.com?"
},
"twoStepContinueToBitwardenUrlDesc": {
"message": "Bitwarden autentifikator omogućuje pohranu ključeva za autentifikaciju i generiranje TOTP kodova za dvostruku autentifikaciju. Saznaj više na bitwarden.com."
"message": "Bitwarden autentifikator omogućuje pohranu ključeva za autentifikator i generiranje TOTP kodova za provjeru dvostruke autentifikacije. Saznaj više na bitwarden.com."
},
"twoStepAuthenticatorScanCodeV2": {
"message": "Skeniraj donji QR kôd svojom autentifikatorskom aplikacijom ili unesi ključ."
@@ -8748,10 +8748,10 @@
"message": "Nemaš pristup za upravljanje ovom zbirkom."
},
"grantManageCollectionWarningTitle": {
"message": "Missing Manage Collection Permissions"
"message": "Nedostaju prava za upravljanje zbirkom"
},
"grantManageCollectionWarning": {
"message": "Grant Manage collection permissions to allow full collection management including deletion of collection."
"message": "Dodijeli potpuna prava upravljanja zbirkom što uključuje i brisanje."
},
"grantCollectionAccess": {
"message": "Dodijeli grupama i članovima pristup ovoj zbirki."
@@ -10298,13 +10298,13 @@
}
},
"accountDeprovisioningNotification": {
"message": "Administrators now have the ability to delete member accounts that belong to a claimed domain."
"message": "Administratori sada imaju mogućnost obrisati članski račun koji pripada potvrđenoj domeni."
},
"deleteManagedUserWarningDesc": {
"message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
"message": "Ovo će obrisati članski račun uključujući sve stavke u trezoru. Ovo zamjenjuje prethodnu radnju „Ukloni”."
},
"deleteManagedUserWarning": {
"message": "Delete is a new action!"
"message": "Brisanje je nova radnja!"
},
"seatsRemaining": {
"message": "Imaš još $REMAINING$ preostalih sjedišta od $TOTAL$ dodijeljenih ovog organizaciji. Kontaktiraj svog pružatelja usluge za upravljanje pretplatom.",

View File

@@ -1882,22 +1882,22 @@
"message": "Pieteikšanās jaunā ierīcē"
},
"turnOffNewDeviceLoginProtection": {
"message": "Turn off new device login protection"
"message": "Izslēgt pieteikšanās jaunā ierīcē aizsardzību"
},
"turnOnNewDeviceLoginProtection": {
"message": "Turn on new device login protection"
"message": "Ieslēgt pieteikšanās jaunā ierīcē aizsardzību"
},
"turnOffNewDeviceLoginProtectionModalDesc": {
"message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device."
"message": "Turpināt zemāk, lai izslēgtu apliecināšanas e-pasta ziņojumus, ko Bitwarden sūta, kad notiek pieteikšanās jaunā ierīcē."
},
"turnOnNewDeviceLoginProtectionModalDesc": {
"message": "Proceed below to have bitwarden send you verification emails when you login from a new device."
"message": "Turpināt zemāk, lai Bitwarden sūtītu apliecināšanas e-pasta ziņojumus, kad notiek pieteikšanās jaunā ierīcē."
},
"turnOffNewDeviceLoginProtectionWarning": {
"message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login."
"message": "Ar izslēgtu pieteikšanās jaunā ierīcē aizsardzību ikviens ar Tavu galveno paroli var piekļūt kontam no jebkuras ierīces. Lai aizsargātu savu kontu bez apliecināšanas e-pasta ziņojumiem, jāiestata divpakāpju pieteikšanās."
},
"accountNewDeviceLoginProtectionSaved": {
"message": "New device login protection changes saved"
"message": "Pieteikšanās jaunā ierīcē aizsardzības izmaiņas saglabātas"
},
"sessionsDeauthorized": {
"message": "Visu sesiju darbība ir atsaukta"
@@ -10212,7 +10212,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForSentSponsorship": {
"message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?",
"message": "Ja tiks noņemta $EMAIL$, atbalstītājdarbība šim Ģimenes plānam nevarēs izmantot. Vai tiešām turpināt?",
"placeholders": {
"email": {
"content": "$1",
@@ -10221,7 +10221,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": {
"message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?",
"message": "Ja tiks noņemta $EMAIL$, šī Ģimenes plāna atbalstītājdarbība beigsies, un $DATE$ ar saglabāto maksājumu veidu tiks veikta $40 apmaksa + piemērojamais nodoklis. Nebūs iespējams izmantot jaunu atbalstītājdarbību līdz $DATE$. Vai tiešām turpināt?",
"placeholders": {
"email": {
"content": "$1",

View File

@@ -1399,13 +1399,13 @@
"message": "Toegang weigeren"
},
"notificationSentDeviceAnchor": {
"message": "web-app"
"message": "webapp"
},
"notificationSentDevicePart2": {
"message": "Zorg ervoor dat de Vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt."
"message": "Zorg ervoor dat de vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt."
},
"notificationSentDeviceComplete": {
"message": "Ontgrendel Bitwarden op je apparaat. Zorg ervoor dat de vingerafdruk-zin overeenkomt met de onderstaande zin voordat je deze goedkeurt."
"message": "Ontgrendel Bitwarden op je apparaat. Zorg ervoor dat de vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt."
},
"aNotificationWasSentToYourDevice": {
"message": "Er is een melding naar je apparaat verzonden"
@@ -1888,10 +1888,10 @@
"message": "Inlogbescherming nieuwe apparaten inschakelen"
},
"turnOffNewDeviceLoginProtectionModalDesc": {
"message": "Ga hieronder verder voor het uitschakelen van de e-mailverificatieberichten die Bitwarden stuurt wanneer je inlogt vanaf een nieuw apparaat."
"message": "Ga hieronder verder voor het uitschakelen van de verificatie e-mails die Bitwarden stuurt wanneer je inlogt vanaf een nieuw apparaat."
},
"turnOnNewDeviceLoginProtectionModalDesc": {
"message": "Ga hieronder verder om Bitwarden verificatie e-mails te sturen wanneer je inlogt vanaf een nieuw apparaat.\n\nGa hieronder verder om Bitwarden e-mailverificatieberichten te laten sturen wanneer je inlogt vanaf een nieuw apparaat."
"message": "Ga hieronder verder om Bitwarden verificatie e-mails te sturen wanneer je inlogt vanaf een nieuw apparaat."
},
"turnOffNewDeviceLoginProtectionWarning": {
"message": "Als je inlogbescherming voor nieuwe apparaten uitschakelt, kan iedereen op ieder apparaat met je hoofdwachtwoord inloggen. Stel tweestapsaanmelding in om je account te beschermen zonder e-mailverificatieberichten."
@@ -10320,7 +10320,7 @@
}
},
"existingOrganization": {
"message": "Bestaande toevoegen"
"message": "Bestaande organisatie"
},
"selectOrganizationProviderPortal": {
"message": "Kies een organisatie om aan je providerportaal toe te voegen."

View File

@@ -8748,10 +8748,10 @@
"message": "Não tem acesso para gerir esta coleção."
},
"grantManageCollectionWarningTitle": {
"message": "Missing Manage Collection Permissions"
"message": "Permissões de gestão de coleções em falta"
},
"grantManageCollectionWarning": {
"message": "Grant Manage collection permissions to allow full collection management including deletion of collection."
"message": "Conceda permissões Gerir coleção para permitir a gestão completa da coleção, incluindo a eliminação da mesma."
},
"grantCollectionAccess": {
"message": "Conceder a grupos ou membros acesso a esta coleção."
@@ -10298,13 +10298,13 @@
}
},
"accountDeprovisioningNotification": {
"message": "Administrators now have the ability to delete member accounts that belong to a claimed domain."
"message": "Os administradores têm agora a capacidade de eliminar contas de membros que pertençam a um domínio reivindicado."
},
"deleteManagedUserWarningDesc": {
"message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
"message": "Esta ação elimina a conta do membro, incluindo todos os itens no seu cofre. Esta ação substitui a anterior Remover."
},
"deleteManagedUserWarning": {
"message": "Delete is a new action!"
"message": "Eliminar é uma nova ação!"
},
"seatsRemaining": {
"message": "Tem $REMAINING$ lugares restantes dos $TOTAL$ lugares atribuídos a esta organização. Contacte o seu fornecedor para gerir a sua subscrição.",

View File

@@ -8635,7 +8635,7 @@
"message": "Ограничить удаление коллекций владельцам и администраторам"
},
"limitItemDeletionDesc": {
"message": "Ограничить удаление элементов для пользователей с разрешением 'Может управлять'"
"message": "Ограничить удаление элементов для пользователей с разрешением «Может управлять»"
},
"allowAdminAccessToAllCollectionItemsDesc": {
"message": "Владельцы и администраторы могут управлять всеми коллекциями и элементами"
@@ -10341,7 +10341,7 @@
}
},
"addedExistingOrganization": {
"message": "Added existing organization"
"message": "Добавлена ​​существующая организация"
},
"assignedExceedsAvailable": {
"message": "Assigned seats exceed available seats."

View File

@@ -1,24 +1,24 @@
{
"allApplications": {
"message": "All applications"
"message": "Sve aplikacije"
},
"criticalApplications": {
"message": "Critical applications"
"message": "Kritične aplikacije"
},
"accessIntelligence": {
"message": "Access Intelligence"
"message": "Pristupi inteligenciji"
},
"riskInsights": {
"message": "Risk Insights"
"message": "Uvid u rizik"
},
"passwordRisk": {
"message": "Password Risk"
"message": "Rizik od lozinke"
},
"reviewAtRiskPasswords": {
"message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords."
"message": "Pregledaj rizične lozinke (slabe, izložene ili ponovo korišćene) u aplikacijama. Izaberi svoje najkritičnije aplikacije da bi dao prioritet bezbednosnim radnjama kako bi tvoji korisnici adresirali rizične lozinke."
},
"dataLastUpdated": {
"message": "Data last updated: $DATE$",
"message": "Podaci su poslednji put ažurirani: $DATE$",
"placeholders": {
"date": {
"content": "$1",
@@ -27,19 +27,19 @@
}
},
"notifiedMembers": {
"message": "Notified members"
"message": "Obavešteni članovi"
},
"revokeMembers": {
"message": "Revoke members"
"message": "Ukloni članove"
},
"restoreMembers": {
"message": "Restore members"
"message": "Vrati članove"
},
"cannotRestoreAccessError": {
"message": "Cannot restore organization access"
"message": "Nije moguće povratiti pristup organizaciji"
},
"allApplicationsWithCount": {
"message": "All applications ($COUNT$)",
"message": "Sve aplikacije ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -48,10 +48,10 @@
}
},
"createNewLoginItem": {
"message": "Create new login item"
"message": "Kreirajte novu stavku za prijavu"
},
"criticalApplicationsWithCount": {
"message": "Critical applications ($COUNT$)",
"message": "Kritične aplikacije ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -60,7 +60,7 @@
}
},
"notifiedMembersWithCount": {
"message": "Notified members ($COUNT$)",
"message": "Obavešteni članovi ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -69,7 +69,7 @@
}
},
"noAppsInOrgTitle": {
"message": "No applications found in $ORG NAME$",
"message": "Nije pronađena nijedna aplikacija u $ORG NAME$",
"placeholders": {
"org name": {
"content": "$1",
@@ -78,7 +78,7 @@
}
},
"noAppsInOrgDescription": {
"message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords."
"message": "Dok korisnici čuvaju prijave, aplikacije se pojavljuju ovde, prikazujući sve rizične lozinke. Označite kritične aplikacije i obavestite korisnike da ažuriraju lozinke."
},
"noCriticalAppsTitle": {
"message": "You haven't marked any applications as a Critical"

View File

@@ -2090,7 +2090,7 @@
"message": "Показувати піктограми вебсайтів"
},
"faviconDesc": {
"message": "Показувати впізнаване зображення біля кожного запису."
"message": "Показувати зображення біля кожного запису."
},
"default": {
"message": "Типово"

View File

@@ -1402,10 +1402,10 @@
"message": "网页 App"
},
"notificationSentDevicePart2": {
"message": "在批准前,请确保指纹短语与下面的一致。"
"message": "在批准前,请确保指纹短语与下面的相匹配。"
},
"notificationSentDeviceComplete": {
"message": "在您的设备上解锁 Bitwarden。在批准前请确保指纹短语与下面的一致。"
"message": "在您的设备上解锁 Bitwarden。在批准前请确保指纹短语与下面的相匹配。"
},
"aNotificationWasSentToYourDevice": {
"message": "通知已发送到您的设备"
@@ -2959,7 +2959,7 @@
"description": "Noun. A refunded payment that was charged."
},
"chargesStatement": {
"message": "任何费用将以 $STATEMENT_NAME$ 出现在您的账单上。",
"message": "任何费用将以 $STATEMENT_NAME$ 出现在您的账单上。",
"placeholders": {
"statement_name": {
"content": "$1",
@@ -5125,7 +5125,7 @@
"description": "This text will be displayed after a Send has been accessed the maximum amount of times."
},
"pendingDeletion": {
"message": "待删除"
"message": "待删除"
},
"expired": {
"message": "已过期"
@@ -7365,7 +7365,7 @@
"description": "Message to encourage the user to start creating service accounts."
},
"serviceAccountsNoItemsTitle": {
"message": "还没有显示的内容",
"message": "还没有显示的内容",
"description": "Title to indicate that there are no service accounts to display."
},
"searchSecrets": {
@@ -9002,7 +9002,7 @@
"description": "Message to encourage the user to start creating machine accounts."
},
"machineAccountsNoItemsTitle": {
"message": "暂无要显示的内容",
"message": "还没有可显示的内容",
"description": "Title to indicate that there are no machine accounts to display."
},
"deleteMachineAccounts": {
@@ -10089,10 +10089,10 @@
"message": "不允许成员通过此组织兑换家庭计划。"
},
"verifyBankAccountWithStatementDescriptorWarning": {
"message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在组织的计费页面输入该转账的语句描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。"
"message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在组织的计费页面输入该转账的对账单描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。"
},
"verifyBankAccountWithStatementDescriptorInstructions": {
"message": "我们已向您的银行账户存入了一笔小额转账(可能需要 1-2 个工作日到账)。请输入转账说明中以 \"SM\" 开头的六位数代码。验证银行账户失败将会错过支付,您的订阅将失效。"
"message": "我们已向您的银行账户存入了一笔小额转账(可能需要 1-2 个工作日到账)。请输入转账描述中以「SM」开头的六位数代码。验证银行账户失败将会错过支付,您的订阅将失效。"
},
"descriptorCode": {
"message": "描述符代码"

View File

@@ -11,6 +11,4 @@ if (process.env.NODE_ENV === "production") {
enableProdMode();
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
void platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -35,8 +35,7 @@
}
},
"angularCompilerOptions": {
"strictTemplates": true,
"preserveWhitespaces": true
"strictTemplates": true
},
"files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"],
"include": [