diff --git a/jslib b/jslib index ff8c1dfea9..278b4402da 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit ff8c1dfea9a3a6e42e2191dd3816889ce43045e0 +Subproject commit 278b4402da94ab36b4def76f520599a23308f7f1 diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 07075049b0..8b0c670c14 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -20,6 +20,7 @@ import { AccountComponent } from './settings/account.component'; import { CreateOrganizationComponent } from './settings/create-organization.component'; import { DomainRulesComponent } from './settings/domain-rules.component'; import { OptionsComponent } from './settings/options.component'; +import { OrganizationsComponent } from './settings/organizations.component'; import { PremiumComponent } from './settings/premium.component'; import { SettingsComponent } from './settings/settings.component'; import { TwoFactorSetupComponent } from './settings/two-factor-setup.component'; @@ -65,6 +66,7 @@ const routes: Routes = [ { path: 'two-factor', component: TwoFactorSetupComponent, canActivate: [AuthGuardService] }, { path: 'premium', component: PremiumComponent, canActivate: [AuthGuardService] }, { path: 'billing', component: UserBillingComponent, canActivate: [AuthGuardService] }, + { path: 'organizations', component: OrganizationsComponent, canActivate: [AuthGuardService] }, { path: 'create-organization', component: CreateOrganizationComponent, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8189126f11..37eed3ad49 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -46,6 +46,7 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co import { DeleteAccountComponent } from './settings/delete-account.component'; import { DomainRulesComponent } from './settings/domain-rules.component'; import { OptionsComponent } from './settings/options.component'; +import { OrganizationsComponent } from './settings/organizations.component'; import { PaymentComponent } from './settings/payment.component'; import { PremiumComponent } from './settings/premium.component'; import { ProfileComponent } from './settings/profile.component'; @@ -78,7 +79,6 @@ import { CiphersComponent } from './vault/ciphers.component'; import { CollectionsComponent } from './vault/collections.component'; import { FolderAddEditComponent } from './vault/folder-add-edit.component'; import { GroupingsComponent } from './vault/groupings.component'; -import { OrganizationsComponent } from './vault/organizations.component'; import { ShareComponent } from './vault/share.component'; import { VaultComponent } from './vault/vault.component'; diff --git a/src/app/layouts/organization-layout.component.html b/src/app/layouts/organization-layout.component.html index a4f2017377..814888d8cd 100644 --- a/src/app/layouts/organization-layout.component.html +++ b/src/app/layouts/organization-layout.component.html @@ -15,13 +15,13 @@ {{'vault' | i18n}} - - diff --git a/src/app/organizations/ciphers.component.ts b/src/app/organizations/ciphers.component.ts index cbaa27ca83..d3ea4d7fc1 100644 --- a/src/app/organizations/ciphers.component.ts +++ b/src/app/organizations/ciphers.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, + Input, Output, } from '@angular/core'; @@ -26,6 +27,7 @@ import { CipherView } from 'jslib/models/view/cipherView'; templateUrl: '../vault/ciphers.component.html', }) export class CiphersComponent extends BaseCiphersComponent { + @Input() showAddNew = true; @Output() onAttachmentsClicked = new EventEmitter(); @Output() onCollectionsClicked = new EventEmitter(); @@ -58,7 +60,16 @@ export class CiphersComponent extends BaseCiphersComponent { this.applyFilter(filter); this.loaded = true; } else { - await super.load((c) => c.organizationId === this.organization.id && (filter == null || filter(c))); + await super.load(); + } + } + + applyFilter(filter: (cipher: CipherView) => boolean = null) { + if (this.organization.isAdmin) { + super.applyFilter(filter); + } else { + const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c)); + super.applyFilter(f); } } diff --git a/src/app/organizations/groupings.component.ts b/src/app/organizations/groupings.component.ts index 067156de23..9e22af6f3b 100644 --- a/src/app/organizations/groupings.component.ts +++ b/src/app/organizations/groupings.component.ts @@ -54,15 +54,15 @@ export class GroupingsComponent extends BaseGroupingsComponent { } else { this.collections = []; } + + const unassignedCollection = new CollectionView(); + unassignedCollection.name = this.i18nService.t('unassigned'); + unassignedCollection.id = 'unassigned'; + unassignedCollection.organizationId = this.organization.id; + unassignedCollection.readOnly = true; + this.collections.push(unassignedCollection); } else { await super.loadCollections(this.organization.id); } - - const unassignedCollection = new CollectionView(); - unassignedCollection.name = this.i18nService.t('unassigned'); - unassignedCollection.id = 'unassigned'; - unassignedCollection.organizationId = this.organization.id; - unassignedCollection.readOnly = true; - this.collections.push(unassignedCollection); } } diff --git a/src/app/organizations/vault.component.ts b/src/app/organizations/vault.component.ts index 62bd0a78e5..522b6e5f6c 100644 --- a/src/app/organizations/vault.component.ts +++ b/src/app/organizations/vault.component.ts @@ -71,6 +71,7 @@ export class VaultComponent implements OnInit { } async clearGroupingFilters() { + this.ciphersComponent.showAddNew = true; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault'); await this.ciphersComponent.applyFilter(); this.clearFilters(); @@ -78,6 +79,7 @@ export class VaultComponent implements OnInit { } async filterCipherType(type: CipherType, load = false) { + this.ciphersComponent.showAddNew = true; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType'); const filter = (c: CipherView) => c.type === type; if (load) { @@ -91,6 +93,7 @@ export class VaultComponent implements OnInit { } async filterCollection(collectionId: string, load = false) { + this.ciphersComponent.showAddNew = false; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection'); const filter = (c: CipherView) => { if (collectionId === 'unassigned') { diff --git a/src/app/settings/organizations.component.html b/src/app/settings/organizations.component.html new file mode 100644 index 0000000000..fddd4c1650 --- /dev/null +++ b/src/app/settings/organizations.component.html @@ -0,0 +1,62 @@ + +

+ +

+ + +

{{'noOrganizationsList' | i18n}}

+
+ + + {{'newOrganization' | i18n}} + +
+ + + + +

{{'noOrganizationsList' | i18n}}

+ + + + + + + + +
+ + + {{o.name}} + + +
+
+
diff --git a/src/app/settings/organizations.component.ts b/src/app/settings/organizations.component.ts new file mode 100644 index 0000000000..13b950c6fe --- /dev/null +++ b/src/app/settings/organizations.component.ts @@ -0,0 +1,61 @@ +import { + Component, + Input, + OnInit, +} from '@angular/core'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { SyncService } from 'jslib/abstractions/sync.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { Organization } from 'jslib/models/domain/organization'; + +@Component({ + selector: 'app-organizations', + templateUrl: 'organizations.component.html', +}) +export class OrganizationsComponent implements OnInit { + @Input() vault = false; + + organizations: Organization[]; + loaded: boolean = false; + actionPromise: Promise; + + constructor(private userService: UserService, private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, private apiService: ApiService, + private analytics: Angulartics2, private toasterService: ToasterService, + private syncService: SyncService) { } + + async ngOnInit() { + await this.load(); + } + + async load() { + this.organizations = await this.userService.getAllOrganizations(); + this.loaded = true; + } + + async leave(org: Organization) { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('leaveOrganizationConfirmation'), org.name, + this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); + if (!confirmed) { + return false; + } + + try { + this.actionPromise = this.apiService.postLeaveOrganization(org.id).then(() => { + return this.syncService.fullSync(true); + }); + await this.actionPromise; + this.analytics.eventTrack.next({ action: 'Left Organization' }); + this.toasterService.popAsync('success', null, this.i18nService.t('leftOrganization')); + await this.load(); + } catch { } + } +} diff --git a/src/app/vault/ciphers.component.html b/src/app/vault/ciphers.component.html index fbfc66f15c..bcb09d70ea 100644 --- a/src/app/vault/ciphers.component.html +++ b/src/app/vault/ciphers.component.html @@ -52,7 +52,7 @@

{{'noItemsInList' | i18n}}

-
diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts index acc237fe5b..96ef5376dc 100644 --- a/src/app/vault/ciphers.component.ts +++ b/src/app/vault/ciphers.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, + Input, Output, } from '@angular/core'; @@ -24,6 +25,7 @@ const MaxCheckedCount = 500; templateUrl: 'ciphers.component.html', }) export class CiphersComponent extends BaseCiphersComponent { + @Input() showAddNew = true; @Output() onAttachmentsClicked = new EventEmitter(); @Output() onShareClicked = new EventEmitter(); @Output() onCollectionsClicked = new EventEmitter(); diff --git a/src/app/vault/organizations.component.html b/src/app/vault/organizations.component.html deleted file mode 100644 index 62302e46be..0000000000 --- a/src/app/vault/organizations.component.html +++ /dev/null @@ -1,17 +0,0 @@ -

- -

- - -

{{'noOrganizationsList' | i18n}}

-
- - - {{'newOrganization' | i18n}} - diff --git a/src/app/vault/organizations.component.ts b/src/app/vault/organizations.component.ts deleted file mode 100644 index 8c8cb81034..0000000000 --- a/src/app/vault/organizations.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - Component, - OnInit, -} from '@angular/core'; - -import { UserService } from 'jslib/abstractions/user.service'; - -import { Organization } from 'jslib/models/domain/organization'; - -@Component({ - selector: 'app-vault-organizations', - templateUrl: 'organizations.component.html', -}) -export class OrganizationsComponent implements OnInit { - organizations: Organization[]; - loaded: boolean = false; - - constructor(private userService: UserService) { } - - async ngOnInit() { - await this.load(); - } - - async load() { - this.organizations = await this.userService.getAllOrganizations(); - this.loaded = true; - } -} diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html index 329d87617b..492092f7ce 100644 --- a/src/app/vault/vault.component.html +++ b/src/app/vault/vault.component.html @@ -39,7 +39,7 @@ - @@ -56,10 +56,10 @@
- Organizations + {{'organizations' | i18n}}
- +
diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts index 6011fb80c8..f631aa0272 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts @@ -91,6 +91,7 @@ export class VaultComponent implements OnInit { } async clearGroupingFilters() { + this.ciphersComponent.showAddNew = true; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault'); await this.ciphersComponent.load(); this.clearFilters(); @@ -98,6 +99,7 @@ export class VaultComponent implements OnInit { } async filterFavorites() { + this.ciphersComponent.showAddNew = true; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFavorites'); await this.ciphersComponent.load((c) => c.favorite); this.clearFilters(); @@ -106,6 +108,7 @@ export class VaultComponent implements OnInit { } async filterCipherType(type: CipherType) { + this.ciphersComponent.showAddNew = true; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType'); await this.ciphersComponent.load((c) => c.type === type); this.clearFilters(); @@ -114,6 +117,7 @@ export class VaultComponent implements OnInit { } async filterFolder(folderId: string) { + this.ciphersComponent.showAddNew = true; folderId = folderId === 'none' ? null : folderId; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFolder'); await this.ciphersComponent.load((c) => c.folderId === folderId); @@ -123,6 +127,7 @@ export class VaultComponent implements OnInit { } async filterCollection(collectionId: string) { + this.ciphersComponent.showAddNew = false; this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection'); await this.ciphersComponent.load((c) => c.collectionIds.indexOf(collectionId) > -1); this.clearFilters(); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 846cab9e94..5f2c90d7ac 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1659,5 +1659,14 @@ }, "organizationReadyToGo": { "message": "Your new organization is ready to go!" + }, + "leave": { + "message": "Leave" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You have left the organization." } }