mirror of
https://github.com/bitwarden/browser
synced 2026-02-20 11:24:07 +00:00
[PM-31974] - Vault Welcome dialog (#18960)
* premium upgrade prompt and onboarding dialog * finalize onboard vault dialog * vault welcome dialog no ext * finish welcome dialog prompt * revert changes to unified upgrade prompt service * rename component * rename feature flag * add welcome dialog service * fix tests * fix footer position in welcome dialog * present dialog in order * fix tests * fix padding
This commit is contained in:
@@ -2860,6 +2860,9 @@
|
||||
"reviewAtRiskLoginSlideImgAltPeriod": {
|
||||
"message": "Illustration of a list of logins that are at-risk."
|
||||
},
|
||||
"welcomeDialogGraphicAlt": {
|
||||
"message": "Illustration of the layout of the Bitwarden vault page."
|
||||
},
|
||||
"generatePasswordSlideDesc": {
|
||||
"message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
|
||||
"description": "Description of the generate password slide on the at-risk password page carousel"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<bit-dialog dialogSize="large" class="tw-p-0">
|
||||
<div bitDialogContent class="tw-p-2 tw-pb-0">
|
||||
<img
|
||||
class="tw-max-w-full"
|
||||
src="../../../../images/welcome-dialog-graphic.png"
|
||||
[alt]="'welcomeDialogGraphicAlt' | i18n"
|
||||
/>
|
||||
<div class="tw-px-12 tw-pt-6 tw-text-center">
|
||||
<div class="tw-px-12">
|
||||
<h2 bitTypography="h2" class="tw-mb-4">
|
||||
{{ "vaultWelcomeDialogTitle" | i18n }}
|
||||
</h2>
|
||||
<p bitTypography="body1" class="tw-mb-0 tw-text-muted">
|
||||
{{ "vaultWelcomeDialogDescription" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div bitDialogFooter class="tw-w-full tw-flex tw-justify-center tw-gap-4 tw-pb-3">
|
||||
<button bitButton buttonType="secondary" type="button" (click)="onDismiss()">
|
||||
{{ "vaultWelcomeDialogDismissCta" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="primary" type="button" (click)="onPrimaryCta()">
|
||||
{{ "vaultWelcomeDialogPrimaryCta" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogRef } from "@bitwarden/components";
|
||||
import { StateProvider } from "@bitwarden/state";
|
||||
|
||||
import {
|
||||
VaultWelcomeDialogComponent,
|
||||
VaultWelcomeDialogResult,
|
||||
} from "./vault-welcome-dialog.component";
|
||||
|
||||
describe("VaultWelcomeDialogComponent", () => {
|
||||
let component: VaultWelcomeDialogComponent;
|
||||
let fixture: ComponentFixture<VaultWelcomeDialogComponent>;
|
||||
|
||||
const mockUserId = "user-123" as UserId;
|
||||
const activeAccount$ = new BehaviorSubject<Account | null>({
|
||||
id: mockUserId,
|
||||
} as Account);
|
||||
const setUserState = jest.fn().mockResolvedValue([mockUserId, true]);
|
||||
const close = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VaultWelcomeDialogComponent],
|
||||
providers: [
|
||||
{ provide: AccountService, useValue: { activeAccount$ } },
|
||||
{ provide: StateProvider, useValue: { setUserState } },
|
||||
{ provide: DialogRef, useValue: { close } },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VaultWelcomeDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe("onDismiss", () => {
|
||||
it("should set acknowledged state and close with Dismissed result", async () => {
|
||||
await component["onDismiss"]();
|
||||
|
||||
expect(setUserState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ key: "vaultWelcomeDialogAcknowledged" }),
|
||||
true,
|
||||
mockUserId,
|
||||
);
|
||||
expect(close).toHaveBeenCalledWith(VaultWelcomeDialogResult.Dismissed);
|
||||
});
|
||||
|
||||
it("should throw if no active account", async () => {
|
||||
activeAccount$.next(null);
|
||||
|
||||
await expect(component["onDismiss"]()).rejects.toThrow("Null or undefined account");
|
||||
|
||||
expect(setUserState).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("onPrimaryCta", () => {
|
||||
it("should set acknowledged state and close with GetStarted result", async () => {
|
||||
activeAccount$.next({ id: mockUserId } as Account);
|
||||
|
||||
await component["onPrimaryCta"]();
|
||||
|
||||
expect(setUserState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ key: "vaultWelcomeDialogAcknowledged" }),
|
||||
true,
|
||||
mockUserId,
|
||||
);
|
||||
expect(close).toHaveBeenCalledWith(VaultWelcomeDialogResult.GetStarted);
|
||||
});
|
||||
|
||||
it("should throw if no active account", async () => {
|
||||
activeAccount$.next(null);
|
||||
|
||||
await expect(component["onPrimaryCta"]()).rejects.toThrow("Null or undefined account");
|
||||
|
||||
expect(setUserState).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
TypographyModule,
|
||||
CenterPositionStrategy,
|
||||
} from "@bitwarden/components";
|
||||
import { StateProvider, UserKeyDefinition, VAULT_WELCOME_DIALOG_DISK } from "@bitwarden/state";
|
||||
|
||||
export const VaultWelcomeDialogResult = {
|
||||
Dismissed: "dismissed",
|
||||
GetStarted: "getStarted",
|
||||
} as const;
|
||||
|
||||
export type VaultWelcomeDialogResult =
|
||||
(typeof VaultWelcomeDialogResult)[keyof typeof VaultWelcomeDialogResult];
|
||||
|
||||
const VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY = new UserKeyDefinition<boolean>(
|
||||
VAULT_WELCOME_DIALOG_DISK,
|
||||
"vaultWelcomeDialogAcknowledged",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
@Component({
|
||||
selector: "app-vault-welcome-dialog",
|
||||
templateUrl: "./vault-welcome-dialog.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, DialogModule, ButtonModule, TypographyModule, JslibModule],
|
||||
})
|
||||
export class VaultWelcomeDialogComponent {
|
||||
private accountService = inject(AccountService);
|
||||
private stateProvider = inject(StateProvider);
|
||||
|
||||
constructor(private dialogRef: DialogRef<VaultWelcomeDialogResult>) {}
|
||||
|
||||
protected async onDismiss(): Promise<void> {
|
||||
await this.setAcknowledged();
|
||||
this.dialogRef.close(VaultWelcomeDialogResult.Dismissed);
|
||||
}
|
||||
|
||||
protected async onPrimaryCta(): Promise<void> {
|
||||
await this.setAcknowledged();
|
||||
this.dialogRef.close(VaultWelcomeDialogResult.GetStarted);
|
||||
}
|
||||
|
||||
private async setAcknowledged(): Promise<void> {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.stateProvider.setUserState(VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY, true, userId);
|
||||
}
|
||||
|
||||
static open(dialogService: DialogService): DialogRef<VaultWelcomeDialogResult> {
|
||||
return dialogService.open<VaultWelcomeDialogResult>(VaultWelcomeDialogComponent, {
|
||||
disableClose: true,
|
||||
positionStrategy: new CenterPositionStrategy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { UnifiedUpgradePromptService } from "../../billing/individual/upgrade/services";
|
||||
|
||||
import { WebVaultPromptService } from "./web-vault-prompt.service";
|
||||
import { WelcomeDialogService } from "./welcome-dialog.service";
|
||||
|
||||
describe("WebVaultPromptService", () => {
|
||||
let service: WebVaultPromptService;
|
||||
@@ -38,20 +39,33 @@ describe("WebVaultPromptService", () => {
|
||||
);
|
||||
const upsertAutoConfirm = jest.fn().mockResolvedValue(undefined);
|
||||
const organizations$ = jest.fn().mockReturnValue(of([]));
|
||||
const displayUpgradePromptConditionally = jest.fn().mockResolvedValue(undefined);
|
||||
const displayUpgradePromptConditionally = jest.fn().mockResolvedValue(false);
|
||||
const enforceOrganizationDataOwnership = jest.fn().mockResolvedValue(undefined);
|
||||
const conditionallyShowWelcomeDialog = jest.fn().mockResolvedValue(false);
|
||||
const logError = jest.fn();
|
||||
|
||||
let activeAccount$: BehaviorSubject<Account | null>;
|
||||
|
||||
function createAccount(overrides: Partial<Account> = {}): Account {
|
||||
return {
|
||||
id: mockUserId,
|
||||
creationDate: new Date(),
|
||||
...overrides,
|
||||
} as Account;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
activeAccount$ = new BehaviorSubject<Account | null>(createAccount());
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
WebVaultPromptService,
|
||||
{ provide: UnifiedUpgradePromptService, useValue: { displayUpgradePromptConditionally } },
|
||||
{ provide: VaultItemsTransferService, useValue: { enforceOrganizationDataOwnership } },
|
||||
{ provide: PolicyService, useValue: { policies$ } },
|
||||
{ provide: AccountService, useValue: { activeAccount$: of({ id: mockUserId }) } },
|
||||
{ provide: AccountService, useValue: { activeAccount$ } },
|
||||
{
|
||||
provide: AutomaticUserConfirmationService,
|
||||
useValue: { configuration$: configurationAutoConfirm$, upsert: upsertAutoConfirm },
|
||||
@@ -60,6 +74,7 @@ describe("WebVaultPromptService", () => {
|
||||
{ provide: ConfigService, useValue: { getFeatureFlag$ } },
|
||||
{ provide: DialogService, useValue: { open } },
|
||||
{ provide: LogService, useValue: { error: logError } },
|
||||
{ provide: WelcomeDialogService, useValue: { conditionallyShowWelcomeDialog } },
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
} from "../../admin-console/organizations/policies";
|
||||
import { UnifiedUpgradePromptService } from "../../billing/individual/upgrade/services";
|
||||
|
||||
import { WelcomeDialogService } from "./welcome-dialog.service";
|
||||
|
||||
@Injectable()
|
||||
export class WebVaultPromptService {
|
||||
private unifiedUpgradePromptService = inject(UnifiedUpgradePromptService);
|
||||
@@ -31,6 +33,7 @@ export class WebVaultPromptService {
|
||||
private configService = inject(ConfigService);
|
||||
private dialogService = inject(DialogService);
|
||||
private logService = inject(LogService);
|
||||
private welcomeDialogService = inject(WelcomeDialogService);
|
||||
|
||||
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
@@ -46,9 +49,13 @@ export class WebVaultPromptService {
|
||||
async conditionallyPromptUser() {
|
||||
const userId = await firstValueFrom(this.userId$);
|
||||
|
||||
void this.unifiedUpgradePromptService.displayUpgradePromptConditionally();
|
||||
if (await this.unifiedUpgradePromptService.displayUpgradePromptConditionally()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.vaultItemTransferService.enforceOrganizationDataOwnership(userId);
|
||||
await this.vaultItemTransferService.enforceOrganizationDataOwnership(userId);
|
||||
|
||||
await this.welcomeDialogService.conditionallyShowWelcomeDialog();
|
||||
|
||||
this.checkForAutoConfirm();
|
||||
}
|
||||
|
||||
123
apps/web/src/app/vault/services/welcome-dialog.service.spec.ts
Normal file
123
apps/web/src/app/vault/services/welcome-dialog.service.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||
import { StateProvider } from "@bitwarden/state";
|
||||
|
||||
import { VaultWelcomeDialogComponent } from "../components/vault-welcome-dialog/vault-welcome-dialog.component";
|
||||
|
||||
import { WelcomeDialogService } from "./welcome-dialog.service";
|
||||
|
||||
describe("WelcomeDialogService", () => {
|
||||
let service: WelcomeDialogService;
|
||||
|
||||
const mockUserId = "user-123" as UserId;
|
||||
|
||||
const getFeatureFlag = jest.fn().mockResolvedValue(false);
|
||||
const getUserState$ = jest.fn().mockReturnValue(of(false));
|
||||
const mockDialogOpen = jest.spyOn(VaultWelcomeDialogComponent, "open");
|
||||
|
||||
let activeAccount$: BehaviorSubject<Account | null>;
|
||||
|
||||
function createAccount(overrides: Partial<Account> = {}): Account {
|
||||
return {
|
||||
id: mockUserId,
|
||||
creationDate: new Date(),
|
||||
...overrides,
|
||||
} as Account;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockDialogOpen.mockReset();
|
||||
|
||||
activeAccount$ = new BehaviorSubject<Account | null>(createAccount());
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
WelcomeDialogService,
|
||||
{ provide: AccountService, useValue: { activeAccount$ } },
|
||||
{ provide: ConfigService, useValue: { getFeatureFlag } },
|
||||
{ provide: DialogService, useValue: {} },
|
||||
{ provide: StateProvider, useValue: { getUserState$ } },
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(WelcomeDialogService);
|
||||
});
|
||||
|
||||
describe("conditionallyShowWelcomeDialog", () => {
|
||||
it("should not show dialog when no active account", async () => {
|
||||
activeAccount$.next(null);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show dialog when feature flag is disabled", async () => {
|
||||
getFeatureFlag.mockResolvedValueOnce(false);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.PM29437_WelcomeDialog);
|
||||
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show dialog when account has no creation date", async () => {
|
||||
activeAccount$.next(createAccount({ creationDate: undefined }));
|
||||
getFeatureFlag.mockResolvedValueOnce(true);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show dialog when account is older than 30 days", async () => {
|
||||
const overThirtyDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30 - 1000);
|
||||
activeAccount$.next(createAccount({ creationDate: overThirtyDaysAgo }));
|
||||
getFeatureFlag.mockResolvedValueOnce(true);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show dialog when user has already acknowledged it", async () => {
|
||||
activeAccount$.next(createAccount({ creationDate: new Date() }));
|
||||
getFeatureFlag.mockResolvedValueOnce(true);
|
||||
getUserState$.mockReturnValueOnce(of(true));
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show dialog for new user who has not acknowledged", async () => {
|
||||
activeAccount$.next(createAccount({ creationDate: new Date() }));
|
||||
getFeatureFlag.mockResolvedValueOnce(true);
|
||||
getUserState$.mockReturnValueOnce(of(false));
|
||||
mockDialogOpen.mockReturnValue({ closed: of(undefined) } as DialogRef<any>);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show dialog for account created exactly 30 days ago", async () => {
|
||||
const exactlyThirtyDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
activeAccount$.next(createAccount({ creationDate: exactlyThirtyDaysAgo }));
|
||||
getFeatureFlag.mockResolvedValueOnce(true);
|
||||
getUserState$.mockReturnValueOnce(of(false));
|
||||
mockDialogOpen.mockReturnValue({ closed: of(undefined) } as DialogRef<any>);
|
||||
|
||||
await service.conditionallyShowWelcomeDialog();
|
||||
|
||||
expect(mockDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
72
apps/web/src/app/vault/services/welcome-dialog.service.ts
Normal file
72
apps/web/src/app/vault/services/welcome-dialog.service.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { StateProvider, UserKeyDefinition, VAULT_WELCOME_DIALOG_DISK } from "@bitwarden/state";
|
||||
|
||||
import { VaultWelcomeDialogComponent } from "../components/vault-welcome-dialog/vault-welcome-dialog.component";
|
||||
|
||||
const VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY = new UserKeyDefinition<boolean>(
|
||||
VAULT_WELCOME_DIALOG_DISK,
|
||||
"vaultWelcomeDialogAcknowledged",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const THIRTY_DAY_MS = 1000 * 60 * 60 * 24 * 30;
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class WelcomeDialogService {
|
||||
private accountService = inject(AccountService);
|
||||
private configService = inject(ConfigService);
|
||||
private dialogService = inject(DialogService);
|
||||
private stateProvider = inject(StateProvider);
|
||||
|
||||
/**
|
||||
* Conditionally shows the welcome dialog to new users.
|
||||
*
|
||||
* @returns true if the dialog was shown, false otherwise
|
||||
*/
|
||||
async conditionallyShowWelcomeDialog() {
|
||||
const account = await firstValueFrom(this.accountService.activeAccount$);
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
const enabled = await this.configService.getFeatureFlag(FeatureFlag.PM29437_WelcomeDialog);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createdAt = account.creationDate;
|
||||
if (!createdAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ageMs = Date.now() - createdAt.getTime();
|
||||
const isNewUser = ageMs >= 0 && ageMs <= THIRTY_DAY_MS;
|
||||
if (!isNewUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const acknowledged = await firstValueFrom(
|
||||
this.stateProvider
|
||||
.getUserState$(VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY, account.id)
|
||||
.pipe(map((v) => v ?? false)),
|
||||
);
|
||||
|
||||
if (acknowledged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = VaultWelcomeDialogComponent.open(this.dialogService);
|
||||
await firstValueFrom(dialogRef.closed);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
BIN
apps/web/src/images/welcome-dialog-graphic.png
Normal file
BIN
apps/web/src/images/welcome-dialog-graphic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
@@ -12920,6 +12920,18 @@
|
||||
"invalidSendPassword": {
|
||||
"message": "Invalid Send password"
|
||||
},
|
||||
"vaultWelcomeDialogTitle": {
|
||||
"message": "You're in! Welcome to Bitwarden"
|
||||
},
|
||||
"vaultWelcomeDialogDescription": {
|
||||
"message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around."
|
||||
},
|
||||
"vaultWelcomeDialogPrimaryCta": {
|
||||
"message": "Start tour"
|
||||
},
|
||||
"vaultWelcomeDialogDismissCta": {
|
||||
"message": "Skip"
|
||||
},
|
||||
"sendPasswordHelperText": {
|
||||
"message": "Individuals will need to enter the password to view this Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
|
||||
@@ -69,6 +69,7 @@ export enum FeatureFlag {
|
||||
BrowserPremiumSpotlight = "pm-23384-browser-premium-spotlight",
|
||||
MigrateMyVaultToMyItems = "pm-20558-migrate-myvault-to-myitems",
|
||||
PM27632_SdkCipherCrudOperations = "pm-27632-cipher-crud-operations-to-sdk",
|
||||
PM29437_WelcomeDialog = "pm-29437-welcome-dialog-no-ext-prompt",
|
||||
PM31039ItemActionInExtension = "pm-31039-item-action-in-extension",
|
||||
|
||||
/* Platform */
|
||||
@@ -135,6 +136,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.BrowserPremiumSpotlight]: FALSE,
|
||||
[FeatureFlag.PM27632_SdkCipherCrudOperations]: FALSE,
|
||||
[FeatureFlag.MigrateMyVaultToMyItems]: FALSE,
|
||||
[FeatureFlag.PM29437_WelcomeDialog]: FALSE,
|
||||
|
||||
/* Auth */
|
||||
[FeatureFlag.PM23801_PrefetchPasswordPrelogin]: FALSE,
|
||||
|
||||
@@ -212,6 +212,9 @@ export const SETUP_EXTENSION_DISMISSED_DISK = new StateDefinition(
|
||||
web: "disk-local",
|
||||
},
|
||||
);
|
||||
export const VAULT_WELCOME_DIALOG_DISK = new StateDefinition("vaultWelcomeDialog", "disk", {
|
||||
web: "disk-local",
|
||||
});
|
||||
export const VAULT_BROWSER_INTRO_CAROUSEL = new StateDefinition(
|
||||
"vaultBrowserIntroCarousel",
|
||||
"disk",
|
||||
|
||||
Reference in New Issue
Block a user