diff --git a/.eslintrc.json b/.eslintrc.json
index 3a1c197326d..fae64d869e8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
"browser": true,
"webextensions": true
},
- "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular"],
+ "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.eslint.json"],
@@ -18,6 +18,16 @@
"prettier",
"plugin:rxjs/recommended"
],
+ "settings": {
+ "import/parsers": {
+ "@typescript-eslint/parser": [".ts"]
+ },
+ "import/resolver": {
+ "typescript": {
+ "alwaysTryTypes": true
+ }
+ }
+ },
"rules": {
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
@@ -65,6 +75,27 @@
"selector": "CallExpression[callee.name='svgIcon']"
}
],
- "curly": ["error", "all"]
+ "curly": ["error", "all"],
+ "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway
+ "import/no-restricted-paths": [
+ "error",
+ {
+ "zones": [
+ // Do not allow angular/node/electron code to be imported into common
+ {
+ "target": "./libs/common/**/*",
+ "from": "./libs/angular/**/*"
+ },
+ {
+ "target": "./libs/common/**/*",
+ "from": "./libs/node/**/*"
+ },
+ {
+ "target": "./libs/common/**/*",
+ "from": "./libs/electron/**/*"
+ }
+ ]
+ }
+ ]
}
}
diff --git a/.github/workflows/release-qa-web.yml b/.github/workflows/release-qa-web.yml
index 1e31c95cbeb..f02286be2f7 100644
--- a/.github/workflows/release-qa-web.yml
+++ b/.github/workflows/release-qa-web.yml
@@ -34,15 +34,11 @@ jobs:
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with:
workflow: build-web.yml
- path: apps/web
+ path: apps/web/build
workflow_conclusion: success
branch: ${{ github.ref_name }}
artifacts: web-*-cloud-QA.zip
- # This should result in a build directory in the current working directory
- - name: Unzip build asset
- working-directory: apps/web
- run: unzip web-*-cloud-QA.zip
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index a23bfa56501..3749bb4ae00 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2017,7 +2017,7 @@
}
},
"lastSeenOn": {
- "message": "last seen on $DATE$",
+ "message": "last seen on: $DATE$",
"placeholders": {
"date": {
"content": "$1",
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 99114bc007f..cf83b6e78d1 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -16,7 +16,7 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
-import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
@@ -27,6 +27,7 @@ import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstrac
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
+import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
@@ -58,7 +59,7 @@ import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.s
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
-import { OrganizationService } from "@bitwarden/common/services/organization.service";
+import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
import { PolicyApiService } from "@bitwarden/common/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
@@ -68,6 +69,7 @@ import { SendService } from "@bitwarden/common/services/send.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
import { SyncService } from "@bitwarden/common/services/sync/sync.service";
+import { SyncNotifierService } from "@bitwarden/common/services/sync/syncNotifier.service";
import { SystemService } from "@bitwarden/common/services/system.service";
import { TokenService } from "@bitwarden/common/services/token.service";
import { TotpService } from "@bitwarden/common/services/totp.service";
@@ -158,6 +160,7 @@ export default class MainBackground {
folderApiService: FolderApiServiceAbstraction;
policyApiService: PolicyApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
+ syncNotifierService: SyncNotifierServiceAbstraction;
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
backgroundWindow = window;
@@ -298,7 +301,8 @@ export default class MainBackground {
this.cryptoFunctionService,
this.stateService
);
- this.organizationService = new OrganizationService(this.stateService);
+ this.syncNotifierService = new SyncNotifierService();
+ this.organizationService = new OrganizationService(this.stateService, this.syncNotifierService);
this.policyService = new PolicyService(this.stateService, this.organizationService);
this.policyApiService = new PolicyApiService(
this.policyService,
@@ -388,9 +392,9 @@ export default class MainBackground {
this.logService,
this.keyConnectorService,
this.stateService,
- this.organizationService,
this.providerService,
this.folderApiService,
+ this.syncNotifierService,
logoutCallback
);
this.eventService = new EventService(
diff --git a/apps/browser/src/background/service_factories/organization-service.factory.ts b/apps/browser/src/background/service_factories/organization-service.factory.ts
index 87692e64391..2e7c31b596e 100644
--- a/apps/browser/src/background/service_factories/organization-service.factory.ts
+++ b/apps/browser/src/background/service_factories/organization-service.factory.ts
@@ -1,12 +1,17 @@
-import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/abstractions/organization.service";
-import { OrganizationService } from "@bitwarden/common/services/organization.service";
+import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
+import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
+import {
+ syncNotifierServiceFactory,
+ SyncNotifierServiceInitOptions,
+} from "./sync-notifier-service.factory";
type OrganizationServiceFactoryOptions = FactoryOptions;
export type OrganizationServiceInitOptions = OrganizationServiceFactoryOptions &
+ SyncNotifierServiceInitOptions &
StateServiceInitOptions;
export function organizationServiceFactory(
@@ -17,6 +22,10 @@ export function organizationServiceFactory(
cache,
"organizationService",
opts,
- async () => new OrganizationService(await stateServiceFactory(cache, opts))
+ async () =>
+ new OrganizationService(
+ await stateServiceFactory(cache, opts),
+ await syncNotifierServiceFactory(cache, opts)
+ )
);
}
diff --git a/apps/browser/src/background/service_factories/sync-notifier-service.factory.ts b/apps/browser/src/background/service_factories/sync-notifier-service.factory.ts
new file mode 100644
index 00000000000..58699bdff58
--- /dev/null
+++ b/apps/browser/src/background/service_factories/sync-notifier-service.factory.ts
@@ -0,0 +1,17 @@
+import { SyncNotifierService as AbstractSyncNotifierService } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
+import { SyncNotifierService } from "@bitwarden/common/services/sync/syncNotifier.service";
+
+import { FactoryOptions, CachedServices, factory } from "./factory-options";
+
+type SyncNotifierServiceFactoryOptions = FactoryOptions;
+
+export type SyncNotifierServiceInitOptions = SyncNotifierServiceFactoryOptions;
+
+export function syncNotifierServiceFactory(
+ cache: { syncNotifierService?: AbstractSyncNotifierService } & CachedServices,
+ opts: SyncNotifierServiceInitOptions
+): Promise {
+ return factory(cache, "syncNotifierService", opts, () =>
+ Promise.resolve(new SyncNotifierService())
+ );
+}
diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts
index 87c1dd17e93..a79028a26d1 100644
--- a/apps/browser/src/popup/services/services.module.ts
+++ b/apps/browser/src/popup/services/services.module.ts
@@ -26,7 +26,7 @@ import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
diff --git a/apps/browser/src/popup/settings/about.component.html b/apps/browser/src/popup/settings/about.component.html
index ed2ceb40bec..c11b68e9c1f 100644
--- a/apps/browser/src/popup/settings/about.component.html
+++ b/apps/browser/src/popup/settings/about.component.html
@@ -14,7 +14,7 @@
{{ "serverVersion" | i18n }}: {{ this.serverConfig?.version }}
- ({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
+ ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
@@ -24,7 +24,7 @@
{{ "serverVersion" | i18n }} ({{ "thirdParty" | i18n }}):
{{ this.serverConfig?.version }}
- ({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
+ ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
@@ -36,7 +36,7 @@
{{ "serverVersion" | i18n }}
({{ "selfHosted" | i18n }}):
{{ this.serverConfig?.version }}
- ({{ "lastSeenOn" | i18n }}: {{ serverConfig.utcDate | date: "mediumDate" }})
+ ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }})
diff --git a/apps/browser/src/popup/vault/add-edit.component.ts b/apps/browser/src/popup/vault/add-edit.component.ts
index f5062051c2e..d220a24a0d8 100644
--- a/apps/browser/src/popup/vault/add-edit.component.ts
+++ b/apps/browser/src/popup/vault/add-edit.component.ts
@@ -12,7 +12,7 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
diff --git a/apps/browser/src/popup/vault/ciphers.component.ts b/apps/browser/src/popup/vault/ciphers.component.ts
index 4346ad7a668..60a92e238c2 100644
--- a/apps/browser/src/popup/vault/ciphers.component.ts
+++ b/apps/browser/src/popup/vault/ciphers.component.ts
@@ -10,7 +10,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
@@ -78,7 +78,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
async ngOnInit() {
this.searchTypeSearch = !this.platformUtilsService.isSafari();
- this.showOrganizations = await this.organizationService.hasOrganizations();
+ this.showOrganizations = this.organizationService.hasOrganizations();
this.vaultFilter = this.vaultFilterService.getVaultFilter();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => {
diff --git a/apps/browser/src/popup/vault/current-tab.component.ts b/apps/browser/src/popup/vault/current-tab.component.ts
index 2f3569841f4..f3e97dd233d 100644
--- a/apps/browser/src/popup/vault/current-tab.component.ts
+++ b/apps/browser/src/popup/vault/current-tab.component.ts
@@ -4,7 +4,7 @@ import { Router } from "@angular/router";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
@@ -219,7 +219,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
const otherTypes: CipherType[] = [];
const dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
const dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
- this.showOrganizations = await this.organizationService.hasOrganizations();
+ this.showOrganizations = this.organizationService.hasOrganizations();
if (!dontShowCards) {
otherTypes.push(CipherType.Card);
}
diff --git a/apps/browser/src/popup/vault/share.component.html b/apps/browser/src/popup/vault/share.component.html
index ad1447f6f65..dcec42415c0 100644
--- a/apps/browser/src/popup/vault/share.component.html
+++ b/apps/browser/src/popup/vault/share.component.html
@@ -1,70 +1,76 @@
{{ "paymentChargedAnnually" | i18n }}
-
+
+
diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts
index 6e9fa21b1f5..2a2fc4d2786 100644
--- a/apps/web/src/app/settings/settings.component.ts
+++ b/apps/web/src/app/settings/settings.component.ts
@@ -2,7 +2,7 @@ import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
diff --git a/apps/web/src/app/settings/sponsored-families.component.html b/apps/web/src/app/settings/sponsored-families.component.html
index 9bd2bb70274..f0151dfddd0 100644
--- a/apps/web/src/app/settings/sponsored-families.component.html
+++ b/apps/web/src/app/settings/sponsored-families.component.html
@@ -22,7 +22,7 @@
[appApiAction]="formPromise"
[formGroup]="sponsorshipForm"
ngNativeValidate
- *ngIf="anyOrgsAvailable"
+ *ngIf="anyOrgsAvailable$ | async"
>
@@ -34,7 +34,9 @@
required
>
-
+
@@ -74,7 +76,7 @@
-
+
@@ -86,12 +88,12 @@
-
+
diff --git a/apps/web/src/app/settings/sponsored-families.component.ts b/apps/web/src/app/settings/sponsored-families.component.ts
index d2233fc6c8a..b0cf27e9639 100644
--- a/apps/web/src/app/settings/sponsored-families.component.ts
+++ b/apps/web/src/app/settings/sponsored-families.component.ts
@@ -1,30 +1,40 @@
-import { Component, OnInit } from "@angular/core";
-import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
+import { Component, OnDestroy, OnInit } from "@angular/core";
+import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
+import { map, Observable, Subject, takeUntil } from "rxjs";
import { notAllowedValueAsync } from "@bitwarden/angular/validators/notAllowedValueAsync.validator";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType";
import { Organization } from "@bitwarden/common/models/domain/organization";
+interface RequestSponsorshipForm {
+ selectedSponsorshipOrgId: FormControl;
+ sponsorshipEmail: FormControl;
+}
+
@Component({
selector: "app-sponsored-families",
templateUrl: "sponsored-families.component.html",
})
-export class SponsoredFamiliesComponent implements OnInit {
+export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
loading = false;
- availableSponsorshipOrgs: Organization[] = [];
- activeSponsorshipOrgs: Organization[] = [];
+ availableSponsorshipOrgs$: Observable;
+ activeSponsorshipOrgs$: Observable;
+ anyOrgsAvailable$: Observable;
+ anyActiveSponsorships$: Observable;
// Conditional display properties
- formPromise: Promise;
+ formPromise: Promise;
- sponsorshipForm: UntypedFormGroup;
+ sponsorshipForm: FormGroup;
+
+ private _destroy = new Subject();
constructor(
private apiService: ApiService,
@@ -32,31 +42,50 @@ export class SponsoredFamiliesComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
private syncService: SyncService,
private organizationService: OrganizationService,
- private formBuilder: UntypedFormBuilder,
+ private formBuilder: FormBuilder,
private stateService: StateService
) {
- this.sponsorshipForm = this.formBuilder.group({
- selectedSponsorshipOrgId: [
- "",
- {
- validators: [Validators.required],
- },
- ],
- sponsorshipEmail: [
- "",
- {
- validators: [Validators.email],
- asyncValidators: [
- notAllowedValueAsync(async () => await this.stateService.getEmail(), true),
- ],
- updateOn: "blur",
- },
- ],
+ this.sponsorshipForm = this.formBuilder.group({
+ selectedSponsorshipOrgId: new FormControl("", {
+ validators: [Validators.required],
+ }),
+ sponsorshipEmail: new FormControl("", {
+ validators: [Validators.email],
+ asyncValidators: [
+ notAllowedValueAsync(async () => await this.stateService.getEmail(), true),
+ ],
+ updateOn: "blur",
+ }),
});
}
async ngOnInit() {
- await this.load();
+ this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
+ map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable))
+ );
+
+ this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => {
+ if (orgs.length === 1) {
+ this.sponsorshipForm.patchValue({
+ selectedSponsorshipOrgId: orgs[0].id,
+ });
+ }
+ });
+
+ this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
+
+ this.activeSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
+ map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null))
+ );
+
+ this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
+
+ this.loading = false;
+ }
+
+ ngOnDestroy(): void {
+ this._destroy.next();
+ this._destroy.complete();
}
async submit() {
@@ -73,50 +102,23 @@ export class SponsoredFamiliesComponent implements OnInit {
this.platformUtilsService.showToast("success", null, this.i18nService.t("sponsorshipCreated"));
this.formPromise = null;
this.resetForm();
- await this.load(true);
+ await this.forceReload();
}
- async load(forceReload = false) {
- if (this.loading) {
- return;
- }
-
+ async forceReload() {
this.loading = true;
- if (forceReload) {
- await this.syncService.fullSync(true);
- }
-
- const allOrgs = await this.organizationService.getAll();
- this.availableSponsorshipOrgs = allOrgs.filter((org) => org.familySponsorshipAvailable);
-
- this.activeSponsorshipOrgs = allOrgs.filter(
- (org) => org.familySponsorshipFriendlyName !== null
- );
-
- if (this.availableSponsorshipOrgs.length === 1) {
- this.sponsorshipForm.patchValue({
- selectedSponsorshipOrgId: this.availableSponsorshipOrgs[0].id,
- });
- }
+ await this.syncService.fullSync(true);
this.loading = false;
}
get sponsorshipEmailControl() {
- return this.sponsorshipForm.controls["sponsorshipEmail"];
+ return this.sponsorshipForm.controls.sponsorshipEmail;
}
private async resetForm() {
this.sponsorshipForm.reset();
}
- get anyActiveSponsorships(): boolean {
- return this.activeSponsorshipOrgs.length > 0;
- }
-
- get anyOrgsAvailable(): boolean {
- return this.availableSponsorshipOrgs.length > 0;
- }
-
get isSelfHosted(): boolean {
return this.platformUtilsService.isSelfHost();
}
diff --git a/apps/web/src/app/settings/two-factor-setup.component.html b/apps/web/src/app/settings/two-factor-setup.component.html
index 931296bdbdf..fb71ac5c394 100644
--- a/apps/web/src/app/settings/two-factor-setup.component.html
+++ b/apps/web/src/app/settings/two-factor-setup.component.html
@@ -92,9 +92,15 @@
{{ "deviceVerificationDesc" | i18n }}
-
+
+
diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts
index 7db6a1911cb..68c558f226d 100644
--- a/apps/web/src/app/shared/shared.module.ts
+++ b/apps/web/src/app/shared/shared.module.ts
@@ -12,7 +12,6 @@ import {
ButtonModule,
CalloutModule,
FormFieldModule,
- SubmitButtonModule,
MenuModule,
TabsModule,
IconModule,
@@ -45,7 +44,6 @@ import "./locales";
ButtonModule,
MenuModule,
FormFieldModule,
- SubmitButtonModule,
IconModule,
TabsModule,
],
@@ -65,7 +63,6 @@ import "./locales";
ButtonModule,
MenuModule,
FormFieldModule,
- SubmitButtonModule,
IconModule,
TabsModule,
],
diff --git a/apps/web/src/app/vault/add-edit.component.ts b/apps/web/src/app/vault/add-edit.component.ts
index 0b7642eb97e..2a01a8ec590 100644
--- a/apps/web/src/app/vault/add-edit.component.ts
+++ b/apps/web/src/app/vault/add-edit.component.ts
@@ -9,7 +9,7 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
diff --git a/apps/web/src/app/vault/bulk-share.component.ts b/apps/web/src/app/vault/bulk-share.component.ts
index eccdf027b2b..abf17fba9b0 100644
--- a/apps/web/src/app/vault/bulk-share.component.ts
+++ b/apps/web/src/app/vault/bulk-share.component.ts
@@ -4,11 +4,12 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipherView";
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
+import { Checkable, isChecked } from "@bitwarden/common/types/checkable";
@Component({
selector: "app-vault-bulk-share",
@@ -20,10 +21,10 @@ export class BulkShareComponent implements OnInit {
@Output() onShared = new EventEmitter();
nonShareableCount = 0;
- collections: CollectionView[] = [];
+ collections: Checkable[] = [];
organizations: Organization[] = [];
shareableCiphers: CipherView[] = [];
- formPromise: Promise;
+ formPromise: Promise;
private writeableCollections: CollectionView[] = [];
@@ -66,9 +67,7 @@ export class BulkShareComponent implements OnInit {
}
async submit() {
- const checkedCollectionIds = this.collections
- .filter((c) => (c as any).checked)
- .map((c) => c.id);
+ const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id);
try {
this.formPromise = this.cipherService.shareManyWithServer(
this.shareableCiphers,
@@ -90,8 +89,8 @@ export class BulkShareComponent implements OnInit {
}
}
- check(c: CollectionView, select?: boolean) {
- (c as any).checked = select == null ? !(c as any).checked : select;
+ check(c: Checkable, select?: boolean) {
+ c.checked = select == null ? !c.checked : select;
}
selectAll(select: boolean) {
@@ -106,7 +105,7 @@ export class BulkShareComponent implements OnInit {
this.collections != null
) {
for (let i = 0; i < this.collections.length; i++) {
- if ((this.collections[i] as any).checked) {
+ if (this.collections[i].checked) {
return true;
}
}
diff --git a/apps/web/src/app/vault/ciphers.component.ts b/apps/web/src/app/vault/ciphers.component.ts
index 0ccb487ac7a..624ea1e65e6 100644
--- a/apps/web/src/app/vault/ciphers.component.ts
+++ b/apps/web/src/app/vault/ciphers.component.ts
@@ -5,7 +5,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
-import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
diff --git a/apps/web/src/app/vault/share.component.html b/apps/web/src/app/vault/share.component.html
index 8d533e12004..77fe2f0ef83 100644
--- a/apps/web/src/app/vault/share.component.html
+++ b/apps/web/src/app/vault/share.component.html
@@ -15,78 +15,87 @@
×
-
- {{ "noOrganizationsList" | i18n }}
-
-
-
{{ "moveToOrgDesc" | i18n }}
-
-
+
diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts
index 3727b08b5b7..a457ed23650 100644
--- a/libs/components/src/dialog/dialog/dialog.component.ts
+++ b/libs/components/src/dialog/dialog/dialog.component.ts
@@ -1,3 +1,4 @@
+import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { Component, Input } from "@angular/core";
@Component({
@@ -7,6 +8,14 @@ import { Component, Input } from "@angular/core";
export class DialogComponent {
@Input() dialogSize: "small" | "default" | "large" = "default";
+ private _disablePadding: boolean;
+ @Input() set disablePadding(value: boolean) {
+ this._disablePadding = coerceBooleanProperty(value);
+ }
+ get disablePadding() {
+ return this._disablePadding;
+ }
+
get width() {
switch (this.dialogSize) {
case "small": {
diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts
index 8e2826111fe..a97b054f358 100644
--- a/libs/components/src/dialog/dialog/dialog.stories.ts
+++ b/libs/components/src/dialog/dialog/dialog.stories.ts
@@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { ButtonModule } from "../../button";
import { IconButtonModule } from "../../icon-button";
import { SharedModule } from "../../shared";
+import { TabsModule } from "../../tabs";
import { I18nMockService } from "../../utils/i18n-mock.service";
import { DialogCloseDirective } from "../directives/dialog-close.directive";
import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive";
@@ -16,7 +17,7 @@ export default {
component: DialogComponent,
decorators: [
moduleMetadata({
- imports: [ButtonModule, SharedModule, IconButtonModule],
+ imports: [ButtonModule, SharedModule, IconButtonModule, TabsModule],
declarations: [DialogTitleContainerDirective, DialogCloseDirective],
providers: [
{
@@ -33,6 +34,13 @@ export default {
args: {
dialogSize: "small",
},
+ argTypes: {
+ _disablePadding: {
+ table: {
+ disable: true,
+ },
+ },
+ },
parameters: {
design: {
type: "figma",
@@ -44,7 +52,7 @@ export default {
const Template: Story
= (args: DialogComponent) => ({
props: args,
template: `
-
+
{{title}}
Dialog body text goes here.
@@ -83,7 +91,7 @@ Large.args = {
const TemplateScrolling: Story
= (args: DialogComponent) => ({
props: args,
template: `
-
+
Scrolling Example
Dialog body text goes here.
@@ -104,3 +112,37 @@ export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
dialogSize: "small",
};
+
+const TemplateTabbed: Story = (args: DialogComponent) => ({
+ props: args,
+ template: `
+
+ Tab Content Example
+
+
+ First Tab Content
+ Second Tab Content
+ Third Tab Content
+
+
+
+
+
+
+
+ `,
+});
+
+export const TabContent = TemplateTabbed.bind({});
+TabContent.args = {
+ dialogSize: "large",
+ disablePadding: true,
+};
+TabContent.story = {
+ parameters: {
+ docs: {
+ storyDescription: `An example of using the \`bitTabGroup\` component within the Dialog. The content padding should be
+ disabled (via \`disablePadding\`) so that the tabs are flush against the dialog title.`,
+ },
+ },
+};
diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts
index cd6f1be402a..6696935257f 100644
--- a/libs/components/src/icon-button/icon-button.component.ts
+++ b/libs/components/src/icon-button/icon-button.component.ts
@@ -72,7 +72,7 @@ const sizes: Record = {
@Component({
selector: "button[bitIconButton]",
- template: ``,
+ template: ``,
})
export class BitIconButtonComponent {
@Input("bitIconButton") icon: string;
@@ -106,10 +106,15 @@ export class BitIconButtonComponent {
"before:tw-rounded-md",
"before:tw-transition",
"before:tw-ring",
+ "before:tw-ring-transparent",
"focus-visible:before:tw-ring-text-contrast",
"focus-visible:tw-z-10",
]
.concat(styles[this.buttonType])
.concat(sizes[this.size]);
}
+
+ get iconClass() {
+ return [this.icon, "!tw-m-0"];
+ }
}
diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts
index 1f1ae6a8d2b..264c655d80f 100644
--- a/libs/components/src/index.ts
+++ b/libs/components/src/index.ts
@@ -7,7 +7,6 @@ export * from "./icon";
export * from "./icon-button";
export * from "./menu";
export * from "./dialog";
-export * from "./submit-button";
export * from "./link";
export * from "./tabs";
export * from "./toggle-group";
diff --git a/libs/components/src/submit-button/index.ts b/libs/components/src/submit-button/index.ts
deleted file mode 100644
index ae7d96d2c1a..00000000000
--- a/libs/components/src/submit-button/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./submit-button.module";
diff --git a/libs/components/src/submit-button/submit-button.component.html b/libs/components/src/submit-button/submit-button.component.html
deleted file mode 100644
index 9d9657ba7ee..00000000000
--- a/libs/components/src/submit-button/submit-button.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
diff --git a/libs/components/src/submit-button/submit-button.component.ts b/libs/components/src/submit-button/submit-button.component.ts
deleted file mode 100644
index 27408349da7..00000000000
--- a/libs/components/src/submit-button/submit-button.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component, HostBinding, Input } from "@angular/core";
-
-import { ButtonTypes } from "../button";
-
-@Component({
- selector: "bit-submit-button",
- templateUrl: "./submit-button.component.html",
-})
-export class SubmitButtonComponent {
- @Input() buttonType: ButtonTypes = "primary";
- @Input() disabled = false;
- @Input() loading: boolean;
-
- @Input() block?: boolean;
-
- @HostBinding("class") get classList() {
- return this.block == null || this.block === false ? [] : ["tw-w-full", "tw-block"];
- }
-}
diff --git a/libs/components/src/submit-button/submit-button.module.ts b/libs/components/src/submit-button/submit-button.module.ts
deleted file mode 100644
index c7ab7567e64..00000000000
--- a/libs/components/src/submit-button/submit-button.module.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { CommonModule } from "@angular/common";
-import { NgModule } from "@angular/core";
-
-import { ButtonModule } from "../button";
-
-import { SubmitButtonComponent } from "./submit-button.component";
-
-@NgModule({
- imports: [CommonModule, ButtonModule],
- exports: [SubmitButtonComponent],
- declarations: [SubmitButtonComponent],
-})
-export class SubmitButtonModule {}
diff --git a/libs/components/src/submit-button/submit-button.stories.ts b/libs/components/src/submit-button/submit-button.stories.ts
deleted file mode 100644
index cf19b1c8e4b..00000000000
--- a/libs/components/src/submit-button/submit-button.stories.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Meta, moduleMetadata, Story } from "@storybook/angular";
-
-import { SubmitButtonComponent } from "./submit-button.component";
-import { SubmitButtonModule } from "./submit-button.module";
-
-export default {
- title: "Component Library/Submit Button",
- component: SubmitButtonComponent,
- decorators: [
- moduleMetadata({
- imports: [SubmitButtonModule],
- }),
- ],
- args: {
- buttonType: "primary",
- loading: false,
- block: false,
- },
- parameters: {
- design: {
- type: "figma",
- url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16733",
- },
- },
-} as Meta;
-
-const Template: Story = (args: SubmitButtonComponent) => ({
- props: args,
- template: `
- Submit
- `,
-});
-
-export const Primary = Template.bind({});
-Primary.args = {};
-
-export const Loading = Template.bind({});
-Loading.args = {
- loading: true,
-};
-
-export const Disabled = Template.bind({});
-Disabled.args = {
- disabled: true,
-};