diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 127e07f25e8..4db8952c92c 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -5187,5 +5187,8 @@
},
"changeAtRiskPassword": {
"message": "Change at-risk password"
+ },
+ "achievements": {
+ "message": "Achievements"
}
}
diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html
index de8ab4c7b08..bd8c025db5a 100644
--- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html
+++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html
@@ -13,6 +13,27 @@
+
+
+
+ Achievements
+
+
+
+
+ See all achievements
+
+
+
+
+
+
{{ "availableAccounts" | i18n }}
diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts
index 78bee121afb..c7db6f89812 100644
--- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts
+++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts
@@ -1,6 +1,6 @@
import { CommonModule, Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
-import { Router } from "@angular/router";
+import { Router, RouterLink } from "@angular/router";
import { Subject, firstValueFrom, map, of, startWith, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@@ -50,6 +50,7 @@ import { AccountSwitcherService } from "./services/account-switcher.service";
SectionComponent,
SectionHeaderComponent,
TypographyModule,
+ RouterLink,
],
})
export class AccountSwitcherComponent implements OnInit, OnDestroy {
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index b33940a68d2..05f656f2f4b 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -71,6 +71,7 @@ import { NotificationsSettingsComponent } from "../autofill/popup/settings/notif
import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component";
import BrowserPopupUtils from "../platform/popup/browser-popup-utils";
import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service";
+import { AchievementsComponent } from "../tools/popup/achievements/achievements.component";
import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component";
import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component";
import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/send-v2/add-edit/send-add-edit.component";
@@ -232,6 +233,12 @@ const routes: Routes = [
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { elevation: 1 } satisfies RouteDataProperties,
},
+ {
+ path: "achievements",
+ component: AchievementsComponent,
+ canActivate: [authGuard],
+ data: { elevation: 1 } satisfies RouteDataProperties,
+ },
{
path: "view-cipher",
component: ViewV2Component,
diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts
index fe6fba85a4b..90eff1e9499 100644
--- a/apps/browser/src/popup/services/init.service.ts
+++ b/apps/browser/src/popup/services/init.service.ts
@@ -2,6 +2,7 @@ import { DOCUMENT } from "@angular/common";
import { inject, Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
+import { AchievementNotifierService } from "@bitwarden/angular/tools/achievements/achievement-notifier.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
@@ -26,6 +27,7 @@ export class InitService {
private logService: LogServiceAbstraction,
private themingService: AbstractThemingService,
private sdkLoadService: SdkLoadService,
+ private achievementNotifierService: AchievementNotifierService,
private viewCacheService: PopupViewCacheService,
@Inject(DOCUMENT) private document: Document,
) {}
@@ -38,6 +40,7 @@ export class InitService {
this.twoFactorService.init();
await this.viewCacheService.init();
await this.sizeService.init();
+ await this.achievementNotifierService.init();
const htmlEl = window.document.documentElement;
this.themingService.applyThemeChangesTo(this.document);
diff --git a/apps/browser/src/tools/popup/achievements/achievements.component.html b/apps/browser/src/tools/popup/achievements/achievements.component.html
new file mode 100644
index 00000000000..73defa852e9
--- /dev/null
+++ b/apps/browser/src/tools/popup/achievements/achievements.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/browser/src/tools/popup/achievements/achievements.component.ts b/apps/browser/src/tools/popup/achievements/achievements.component.ts
new file mode 100644
index 00000000000..3d008bdc8ee
--- /dev/null
+++ b/apps/browser/src/tools/popup/achievements/achievements.component.ts
@@ -0,0 +1,66 @@
+import { CommonModule } from "@angular/common";
+import { Component, OnInit } from "@angular/core";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { EventStoreAbstraction } from "@bitwarden/common/tools/achievements/event-store.abstraction.service";
+import { VaultItems_10_Added_Achievement } from "@bitwarden/common/tools/achievements/examples/achievements";
+import { AchievementEarnedEvent, AchievementId } from "@bitwarden/common/tools/achievements/types";
+import { UserId } from "@bitwarden/common/types/guid";
+import { ButtonModule, IconModule } from "@bitwarden/components";
+
+import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
+import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
+import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
+import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
+
+@Component({
+ templateUrl: "achievements.component.html",
+ standalone: true,
+ imports: [
+ CommonModule,
+ JslibModule,
+ PopupPageComponent,
+ PopupHeaderComponent,
+ PopupFooterComponent,
+ PopOutComponent,
+ ButtonModule,
+ IconModule,
+ ],
+})
+export class AchievementsComponent implements OnInit {
+ private currentUserId: UserId;
+
+ constructor(
+ private eventStore: EventStoreAbstraction,
+ private accountService: AccountService,
+ ) {}
+
+ async ngOnInit() {
+ this.currentUserId = (await firstValueFrom(this.accountService.activeAccount$)).id;
+ }
+
+ testAchievement() {
+ const earnedAchievement: AchievementEarnedEvent = {
+ "@timestamp": Date.now(),
+ event: {
+ kind: "alert",
+ category: "session",
+ },
+ service: {
+ name: "web",
+ type: "client",
+ node: {
+ name: "an-installation-identifier-for-this-client-instance",
+ },
+ environment: "local",
+ version: "2025.3.1-innovation-sprint",
+ },
+ user: { id: this.currentUserId },
+ achievement: { type: "earned", name: VaultItems_10_Added_Achievement.name as AchievementId },
+ };
+
+ this.eventStore.addEvent(earnedAchievement);
+ }
+}