OPS-355: Fix roles accesses (#148)

This commit is contained in:
Rinat Arsaev 2023-09-01 13:54:06 +04:00 committed by GitHub
parent adc9a1a167
commit 452cc3b66d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 158 additions and 65 deletions

View File

@ -61,7 +61,9 @@ export function createApi<
private call(name: keyof T, params: Record<PropertyKey, unknown>) { private call(name: keyof T, params: Record<PropertyKey, unknown>) {
return this.createExtendedParams().pipe( return this.createExtendedParams().pipe(
switchMap((p) => this.api[name](Object.assign({}, params, ...p))), switchMap((extendParams) =>
this.api[name](Object.assign({}, ...extendParams, params)),
),
); );
} }

View File

@ -7,7 +7,9 @@
*ngFor="let roleId of roleIds" *ngFor="let roleId of roleIds"
class="dsh-body-2" class="dsh-body-2"
fxLayoutAlign="center center" fxLayoutAlign="center center"
>{{ (roleIdDict$ | async)?.[roleId] }}</dsh-nested-table-col ><button dsh-button (click)="show(roleId)">
{{ (roleIdDict$ | async)?.[roleId] }}
</button></dsh-nested-table-col
> >
<dsh-nested-table-col *ngIf="isAllowAdd"> <dsh-nested-table-col *ngIf="isAllowAdd">
<button color="accent" dsh-button (click)="add()">{{ t('add') }}</button> <button color="accent" dsh-button (click)="add()">{{ t('add') }}</button>

View File

@ -6,18 +6,20 @@ import {
Input, Input,
OnInit, OnInit,
Output, Output,
OnChanges,
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { MemberRole, ResourceScopeId, RoleId } from '@vality/swag-organizations'; import { ComponentChanges } from '@vality/ng-core';
import { MemberRole, ResourceScopeId, RoleId, Organization } from '@vality/swag-organizations';
import { coerceBoolean } from 'coerce-property'; import { coerceBoolean } from 'coerce-property';
import isNil from 'lodash-es/isNil'; import isNil from 'lodash-es/isNil';
import { BehaviorSubject, combineLatest, EMPTY, Observable, of } from 'rxjs'; import { BehaviorSubject, combineLatest, EMPTY, Observable, of, ReplaySubject } from 'rxjs';
import { first, map, switchMap, tap } from 'rxjs/operators'; import { first, map, switchMap, tap, shareReplay } from 'rxjs/operators';
import { OrganizationsDictionaryService } from '@dsh/app/api/organizations'; import { OrganizationsDictionaryService } from '@dsh/app/api/organizations';
import { ShopsService } from '@dsh/app/api/payments';
import { DialogConfig, DIALOG_CONFIG } from '@dsh/app/sections/tokens'; import { DialogConfig, DIALOG_CONFIG } from '@dsh/app/sections/tokens';
import { ShopsDataService } from '@dsh/app/shared';
import { sortRoleIds } from '@dsh/app/shared/components/organization-roles/utils/sort-role-ids'; import { sortRoleIds } from '@dsh/app/shared/components/organization-roles/utils/sort-role-ids';
import { PartialReadonly } from '@dsh/type-utils'; import { PartialReadonly } from '@dsh/type-utils';
@ -34,7 +36,7 @@ import { SelectRoleDialogData } from './components/select-role-dialog/types/sele
templateUrl: 'change-roles-table.component.html', templateUrl: 'change-roles-table.component.html',
styleUrls: ['change-roles-table.component.scss'], styleUrls: ['change-roles-table.component.scss'],
}) })
export class ChangeRolesTableComponent implements OnInit { export class ChangeRolesTableComponent implements OnInit, OnChanges {
@Input() set roles(roles: PartialReadonly<MemberRole>[]) { @Input() set roles(roles: PartialReadonly<MemberRole>[]) {
if (!isNil(roles)) { if (!isNil(roles)) {
this.roles$.next(roles); this.roles$.next(roles);
@ -44,6 +46,7 @@ export class ChangeRolesTableComponent implements OnInit {
get roles(): PartialReadonly<MemberRole>[] { get roles(): PartialReadonly<MemberRole>[] {
return this.roles$.value; return this.roles$.value;
} }
@Input() organization: Organization;
/** /**
* Edit mode: * Edit mode:
@ -57,8 +60,14 @@ export class ChangeRolesTableComponent implements OnInit {
@Output() addedRoles = new EventEmitter<PartialReadonly<MemberRole>[]>(); @Output() addedRoles = new EventEmitter<PartialReadonly<MemberRole>[]>();
@Output() removedRoles = new EventEmitter<PartialReadonly<MemberRole>[]>(); @Output() removedRoles = new EventEmitter<PartialReadonly<MemberRole>[]>();
organization$ = new ReplaySubject<Organization>(1);
roleIds: RoleId[] = []; roleIds: RoleId[] = [];
shops$ = this.shopsDataService.shops$; shops$ = this.organization$.pipe(
switchMap((organization) =>
this.shopsService.getShopsForParty({ partyID: organization.party }),
),
shareReplay({ bufferSize: 1, refCount: true }),
);
roleIdDict$ = this.organizationsDictionaryService.roleId$; roleIdDict$ = this.organizationsDictionaryService.roleId$;
get availableRoles(): RoleId[] { get availableRoles(): RoleId[] {
@ -71,20 +80,31 @@ export class ChangeRolesTableComponent implements OnInit {
roles$ = new BehaviorSubject<PartialReadonly<MemberRole>[]>([]); roles$ = new BehaviorSubject<PartialReadonly<MemberRole>[]>([]);
isAllowRemoves$ = this.roles$.pipe(map((r) => r.length > 1)); isAllowRemoves$ = this.roles$.pipe(
map(
(roles) =>
!this.editMode || roles.some((r) => roles.some((b) => b.roleId !== r.roleId)),
),
);
private get hasAdminRole() { private get hasAdminRole() {
return !!this.roles.find((r) => r.id === RoleId.Administrator); return !!this.roles.find((r) => r.id === RoleId.Administrator);
} }
constructor( constructor(
private shopsDataService: ShopsDataService, private shopsService: ShopsService,
private dialog: MatDialog, private dialog: MatDialog,
@Inject(DIALOG_CONFIG) private dialogConfig: DialogConfig, @Inject(DIALOG_CONFIG) private dialogConfig: DialogConfig,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private organizationsDictionaryService: OrganizationsDictionaryService, private organizationsDictionaryService: OrganizationsDictionaryService,
) {} ) {}
ngOnChanges({ organization }: ComponentChanges<ChangeRolesTableComponent>) {
if (organization) {
this.organization$.next(organization.currentValue);
}
}
ngOnInit(): void { ngOnInit(): void {
this.roles$.pipe(untilDestroyed(this)).subscribe((roles) => this.selectedRoles.emit(roles)); this.roles$.pipe(untilDestroyed(this)).subscribe((roles) => this.selectedRoles.emit(roles));
} }
@ -116,6 +136,25 @@ export class ChangeRolesTableComponent implements OnInit {
}); });
} }
show(roleId: RoleId) {
const removeDialogsClass = addDialogsClass(this.dialog.openDialogs, 'dsh-hidden');
this.dialog
.open<SelectRoleDialogComponent, SelectRoleDialogData, SelectRoleDialogResult>(
SelectRoleDialogComponent,
{
...this.dialogConfig.large,
data: { availableRoles: [roleId], isShow: true },
},
)
.afterClosed()
.pipe(untilDestroyed(this))
.subscribe({
complete: () => {
removeDialogsClass();
},
});
}
remove(roleId: RoleId): void { remove(roleId: RoleId): void {
this.removeRoleIds([roleId]); this.removeRoleIds([roleId]);
this.removeRoles(this.roles.filter((r) => r.roleId === roleId)); this.removeRoles(this.roles.filter((r) => r.roleId === roleId));

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout'; import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRadioModule } from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio';
import { TranslocoModule } from '@ngneat/transloco'; import { TranslocoModule } from '@ngneat/transloco';
@ -26,6 +27,7 @@ import { SelectRoleDialogComponent } from './components/select-role-dialog/selec
MatRadioModule, MatRadioModule,
ReactiveFormsModule, ReactiveFormsModule,
SelectionModule, SelectionModule,
MatButtonModule,
], ],
declarations: [ChangeRolesTableComponent, SelectRoleDialogComponent], declarations: [ChangeRolesTableComponent, SelectRoleDialogComponent],
exports: [ChangeRolesTableComponent], exports: [ChangeRolesTableComponent],

View File

@ -5,13 +5,13 @@
> >
<mat-radio-group [formControl]="roleControl"> <mat-radio-group [formControl]="roleControl">
<dsh-nested-table [rowsGridTemplateColumns]="rowsGridTemplateColumns"> <dsh-nested-table [rowsGridTemplateColumns]="rowsGridTemplateColumns">
<dsh-nested-table-row> <dsh-nested-table-row *ngIf="!data.isShow">
<dsh-nested-table-col></dsh-nested-table-col> <dsh-nested-table-col></dsh-nested-table-col>
<dsh-nested-table-col *ngFor="let role of roles" fxLayoutAlign="center center"> <dsh-nested-table-col *ngFor="let role of roles" fxLayoutAlign="center center">
<span class="dsh-body-2 header">{{ (roleIdDict$ | async)?.[role] }}</span> <span class="dsh-body-2 header">{{ (roleIdDict$ | async)?.[role] }}</span>
</dsh-nested-table-col> </dsh-nested-table-col>
</dsh-nested-table-row> </dsh-nested-table-row>
<dsh-nested-table-row> <dsh-nested-table-row *ngIf="!data.isShow">
<dsh-nested-table-col></dsh-nested-table-col> <dsh-nested-table-col></dsh-nested-table-col>
<dsh-nested-table-col *ngFor="let role of roles"> <dsh-nested-table-col *ngFor="let role of roles">
<mat-radio-button [value]="role"></mat-radio-button> <mat-radio-button [value]="role"></mat-radio-button>
@ -33,7 +33,7 @@
</dsh-nested-table-row> </dsh-nested-table-row>
</dsh-nested-table> </dsh-nested-table>
</mat-radio-group> </mat-radio-group>
<ng-container dshBaseDialogActions> <ng-container *ngIf="!data.isShow" dshBaseDialogActions>
<button [disabled]="roleControl.invalid" color="accent" dsh-button (click)="select()"> <button [disabled]="roleControl.invalid" color="accent" dsh-button (click)="select()">
{{ t('select') }} {{ t('select') }}
</button> </button>

View File

@ -2,4 +2,5 @@ import { RoleId } from '@vality/swag-organizations';
export interface SelectRoleDialogData { export interface SelectRoleDialogData {
availableRoles: RoleId[]; availableRoles: RoleId[];
isShow?: boolean;
} }

View File

@ -17,7 +17,10 @@
<div fxLayout="column" fxLayoutGap="24px"> <div fxLayout="column" fxLayoutGap="24px">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<h2 class="dsh-title">{{ t('roles') }}</h2> <h2 class="dsh-title">{{ t('roles') }}</h2>
<dsh-change-roles-table (selectedRoles)="selectRoles($event)"></dsh-change-roles-table> <dsh-change-roles-table
[organization]="data.organization"
(selectedRoles)="selectRoles($event)"
></dsh-change-roles-table>
</div> </div>
</div> </div>
<ng-container dshBaseDialogActions> <ng-container dshBaseDialogActions>

View File

@ -37,7 +37,7 @@ export class CreateInvitationDialogComponent {
create() { create() {
return this.invitationsService return this.invitationsService
.createInvitation({ .createInvitation({
orgId: this.data.orgId, orgId: this.data.organization.id,
invitationRequest: { invitationRequest: {
invitee: { invitee: {
contact: { contact: {

View File

@ -1,5 +1,5 @@
import { Organization } from '@vality/swag-organizations'; import { Organization } from '@vality/swag-organizations';
export type CreateInvitationDialogData = { export type CreateInvitationDialogData = {
orgId: Organization['id']; organization: Organization;
}; };

View File

@ -43,7 +43,7 @@ export class InvitationsComponent {
return this.organization$ return this.organization$
.pipe( .pipe(
first(), first(),
switchMap(({ id: orgId }) => switchMap((organization) =>
this.dialog this.dialog
.open< .open<
CreateInvitationDialogComponent, CreateInvitationDialogComponent,
@ -51,7 +51,7 @@ export class InvitationsComponent {
BaseDialogResponseStatus BaseDialogResponseStatus
>(CreateInvitationDialogComponent, { >(CreateInvitationDialogComponent, {
...this.dialogConfig.large, ...this.dialogConfig.large,
data: { orgId }, data: { organization },
}) })
.afterClosed(), .afterClosed(),
), ),

View File

@ -5,6 +5,7 @@
(cancel)="cancel()" (cancel)="cancel()"
> >
<dsh-change-roles-table <dsh-change-roles-table
[organization]="data.organization"
[roles]="roles$ | async" [roles]="roles$ | async"
controlled controlled
editMode editMode

View File

@ -21,7 +21,7 @@ export class EditRolesDialogComponent {
roles$ = defer(() => this.updateRoles$).pipe( roles$ = defer(() => this.updateRoles$).pipe(
switchMap(() => switchMap(() =>
this.membersService this.membersService
.getOrgMember({ orgId: this.data.orgId, userId: this.data.userId }) .getOrgMember({ orgId: this.data.organization.id, userId: this.data.userId })
.pipe(map((r) => r.roles)), .pipe(map((r) => r.roles)),
), ),
untilDestroyed(this), untilDestroyed(this),
@ -45,7 +45,7 @@ export class EditRolesDialogComponent {
return forkJoin( return forkJoin(
roles.map((memberRole) => roles.map((memberRole) =>
this.membersService.assignMemberRole({ this.membersService.assignMemberRole({
orgId: this.data.orgId, orgId: this.data.organization.id,
userId: this.data.userId, userId: this.data.userId,
memberRole, memberRole,
}), }),
@ -65,7 +65,7 @@ export class EditRolesDialogComponent {
roles.map((role) => roles.map((role) =>
this.membersService this.membersService
.removeMemberRole({ .removeMemberRole({
orgId: this.data.orgId, orgId: this.data.organization.id,
userId: this.data.userId, userId: this.data.userId,
memberRoleId: role.id, memberRoleId: role.id,
}) })

View File

@ -1,4 +1,6 @@
import { Organization } from '@vality/swag-organizations';
export interface EditRolesDialogData { export interface EditRolesDialogData {
orgId: string; organization: Organization;
userId: string; userId: string;
} }

View File

@ -82,7 +82,7 @@ export class MemberComponent implements OnChanges {
.open<EditRolesDialogComponent, EditRolesDialogData>(EditRolesDialogComponent, { .open<EditRolesDialogComponent, EditRolesDialogData>(EditRolesDialogComponent, {
...this.dialogConfig.large, ...this.dialogConfig.large,
data: { data: {
orgId: this.organization.id, organization: this.organization,
userId: this.member.id, userId: this.member.id,
}, },
}) })

View File

@ -4,9 +4,10 @@
fxLayoutGap="32px" fxLayoutGap="32px"
> >
<nav [tabPanel]="tabPanel" mat-tab-nav-bar> <nav [tabPanel]="tabPanel" mat-tab-nav-bar>
<ng-container *ngFor="let link of links">
<a <a
#rla="routerLinkActive" #rla="routerLinkActive"
*ngFor="let link of links" *ngIf="link.roles | isAccessAllowed: 'some'"
[active]="rla.isActive" [active]="rla.isActive"
[routerLink]="link.path" [routerLink]="link.path"
mat-tab-link mat-tab-link
@ -14,6 +15,7 @@
> >
<span>{{ link.label$ | async }}</span> <span>{{ link.label$ | async }}</span>
</a> </a>
</ng-container>
</nav> </nav>
<mat-tab-nav-panel #tabPanel> <mat-tab-nav-panel #tabPanel>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -1,6 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco'; import { TranslocoService } from '@ngneat/transloco';
import { RoleAccessName } from '@dsh/app/auth';
@Component({ @Component({
templateUrl: 'integrations.component.html', templateUrl: 'integrations.component.html',
}) })
@ -13,6 +15,7 @@ export class IntegrationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.PaymentLinks],
}, },
{ {
path: 'api-keys', path: 'api-keys',
@ -21,6 +24,7 @@ export class IntegrationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.ApiKeys],
}, },
{ {
path: 'webhooks', path: 'webhooks',
@ -29,6 +33,7 @@ export class IntegrationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.Webhooks],
}, },
]; ];

View File

@ -4,6 +4,7 @@ import { FlexModule } from '@angular/flex-layout';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { TranslocoModule } from '@ngneat/transloco'; import { TranslocoModule } from '@ngneat/transloco';
import { AuthModule } from '@dsh/app/auth';
import { LayoutModule } from '@dsh/components/layout'; import { LayoutModule } from '@dsh/components/layout';
import { ScrollUpModule } from '@dsh/components/navigation'; import { ScrollUpModule } from '@dsh/components/navigation';
@ -19,6 +20,7 @@ import { IntegrationsComponent } from './integrations.component';
TranslocoModule, TranslocoModule,
ScrollUpModule, ScrollUpModule,
MatTabsModule, MatTabsModule,
AuthModule,
], ],
declarations: [IntegrationsComponent], declarations: [IntegrationsComponent],
}) })

View File

@ -5,9 +5,10 @@
fxLayoutGap="32px" fxLayoutGap="32px"
> >
<nav mat-tab-nav-bar> <nav mat-tab-nav-bar>
<ng-container *ngFor="let link of links">
<a <a
#rla="routerLinkActive" #rla="routerLinkActive"
*ngFor="let link of links" *ngIf="link.roles | isAccessAllowed: 'some'"
[active]="rla.isActive" [active]="rla.isActive"
[routerLink]="link.path" [routerLink]="link.path"
mat-tab-link mat-tab-link
@ -15,6 +16,7 @@
> >
<span>{{ link.label$ | async }}</span> <span>{{ link.label$ | async }}</span>
</a> </a>
</ng-container>
</nav> </nav>
<div> <div>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -1,6 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco'; import { TranslocoService } from '@ngneat/transloco';
import { RoleAccessName } from '@dsh/app/auth';
@Component({ @Component({
templateUrl: 'operations.component.html', templateUrl: 'operations.component.html',
}) })
@ -13,6 +15,7 @@ export class OperationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.ViewPayments],
}, },
{ {
path: 'invoices', path: 'invoices',
@ -21,6 +24,7 @@ export class OperationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.ViewInvoices],
}, },
{ {
path: 'refunds', path: 'refunds',
@ -29,6 +33,7 @@ export class OperationsComponent {
null, null,
'payment-section', 'payment-section',
), ),
roles: [RoleAccessName.ViewRefunds],
}, },
]; ];

View File

@ -4,6 +4,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { TranslocoModule } from '@ngneat/transloco'; import { TranslocoModule } from '@ngneat/transloco';
import { AuthModule } from '@dsh/app/auth';
import { LayoutModule } from '@dsh/components/layout'; import { LayoutModule } from '@dsh/components/layout';
import { ScrollUpModule } from '@dsh/components/navigation'; import { ScrollUpModule } from '@dsh/components/navigation';
@ -19,6 +20,7 @@ import { OperationsComponent } from './operations.component';
TranslocoModule, TranslocoModule,
ScrollUpModule, ScrollUpModule,
MatTabsModule, MatTabsModule,
AuthModule,
], ],
declarations: [OperationsComponent], declarations: [OperationsComponent],
}) })

View File

@ -31,11 +31,18 @@ const PAYMENT_SECTION_ROUTES: Routes = [
}, },
[RoleAccessName.ViewAnalytics], [RoleAccessName.ViewAnalytics],
), ),
createPrivateRoute(
{ {
path: 'operations', path: 'operations',
loadChildren: () => loadChildren: () =>
import('./operations/operations.module').then((m) => m.OperationsModule), import('./operations/operations.module').then((m) => m.OperationsModule),
}, },
[
RoleAccessName.ViewPayments,
RoleAccessName.ViewInvoices,
RoleAccessName.ViewRefunds,
],
),
createPrivateRoute( createPrivateRoute(
{ {
path: 'reports', path: 'reports',
@ -51,11 +58,16 @@ const PAYMENT_SECTION_ROUTES: Routes = [
// }, // },
// [RoleAccessName.ViewPayouts] // [RoleAccessName.ViewPayouts]
// ), // ),
createPrivateRoute(
{ {
path: 'integrations', path: 'integrations',
loadChildren: () => loadChildren: () =>
import('./integrations/integrations.module').then((m) => m.IntegrationsModule), import('./integrations/integrations.module').then(
(m) => m.IntegrationsModule,
),
}, },
[RoleAccessName.PaymentLinks, RoleAccessName.ApiKeys, RoleAccessName.Webhooks],
),
], ],
}, },
]; ];

View File

@ -3,7 +3,13 @@
fxLayout="column" fxLayout="column"
fxLayoutGap="32px" fxLayoutGap="32px"
> >
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="end" fxLayoutGap="16px"> <div
*ngIf="'Claims' | isAccessAllowed"
fxLayout="column"
fxLayout.gt-sm="row"
fxLayoutAlign="end"
fxLayoutGap="16px"
>
<button color="accent" dsh-button (click)="createShop()">{{ t('createShop') }}</button> <button color="accent" dsh-button (click)="createShop()">{{ t('createShop') }}</button>
</div> </div>
<dsh-shops-list <dsh-shops-list

View File

@ -4,6 +4,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { TranslocoModule } from '@ngneat/transloco'; import { TranslocoModule } from '@ngneat/transloco';
import { AuthModule } from '@dsh/app/auth';
import { ShopCreationModule } from '@dsh/app/shared/components/shop-creation'; import { ShopCreationModule } from '@dsh/app/shared/components/shop-creation';
import { ButtonModule } from '@dsh/components/buttons'; import { ButtonModule } from '@dsh/components/buttons';
@ -26,6 +27,7 @@ import { ShopsComponent } from './shops.component';
ShopCreationModule, ShopCreationModule,
ButtonModule, ButtonModule,
TranslocoModule, TranslocoModule,
AuthModule,
], ],
declarations: [ShopsComponent], declarations: [ShopsComponent],
exports: [ShopsComponent], exports: [ShopsComponent],

View File

@ -59,7 +59,7 @@ export const toNavbarItemConfig = ({
routerLink: NavbarRouterLink.Reports, routerLink: NavbarRouterLink.Reports,
icon: BootstrapIconName.FileText, icon: BootstrapIconName.FileText,
label: reports, label: reports,
roles: [], roles: [RoleAccessName.Reports],
}, },
{ {
routerLink: NavbarRouterLink.Integrations, routerLink: NavbarRouterLink.Integrations,

View File

@ -5,7 +5,7 @@ import { map, pluck, shareReplay, switchMap } from 'rxjs/operators';
import { MembersService } from '@dsh/app/api/organizations'; import { MembersService } from '@dsh/app/api/organizations';
import { SHARE_REPLAY_CONF } from '@dsh/app/custom-operators'; import { SHARE_REPLAY_CONF } from '@dsh/app/custom-operators';
import { ContextOrganizationService } from '@dsh/app/shared'; import { KeycloakTokenInfoService } from '@dsh/app/shared';
import { Initializable } from '@dsh/app/shared/types'; import { Initializable } from '@dsh/app/shared/types';
@Injectable() @Injectable()
@ -16,16 +16,19 @@ export class OrganizationManagementService implements Initializable {
shareReplay(SHARE_REPLAY_CONF), shareReplay(SHARE_REPLAY_CONF),
); );
isOrganizationOwner$: Observable<boolean> = defer(() => isOrganizationOwner$: Observable<boolean> = defer(() =>
combineLatest([ combineLatest([this.organization$, this.keycloakTokenInfoService.userID$]),
this.organization$,
this.contextOrganizationService.organization$.pipe(pluck('party')),
]),
).pipe( ).pipe(
map(([{ owner }, id]) => owner === id), map(([{ owner }, id]) => owner === id),
shareReplay(SHARE_REPLAY_CONF), shareReplay(SHARE_REPLAY_CONF),
); );
isOrganizationAdmin$: Observable<boolean> = this.contextOrganizationService.member$.pipe( isOrganizationAdmin$: Observable<boolean> = combineLatest([
map((member) => member.roles.findIndex((r) => r.roleId === RoleId.Administrator) !== -1), this.members$,
this.keycloakTokenInfoService.userID$,
]).pipe(
map(([members, userId]) => members.find((m) => m.id === userId)),
map(
(member) => member?.roles?.findIndex?.((r) => r.roleId === RoleId.Administrator) !== -1,
),
shareReplay(SHARE_REPLAY_CONF), shareReplay(SHARE_REPLAY_CONF),
); );
hasAdminAccess$: Observable<boolean> = defer(() => hasAdminAccess$: Observable<boolean> = defer(() =>
@ -39,7 +42,7 @@ export class OrganizationManagementService implements Initializable {
constructor( constructor(
private membersService: MembersService, private membersService: MembersService,
private contextOrganizationService: ContextOrganizationService, private keycloakTokenInfoService: KeycloakTokenInfoService,
) {} ) {}
init(organization: Organization) { init(organization: Organization) {

View File

@ -115,7 +115,7 @@
"manageWebhooks": "Manage webhooks", "manageWebhooks": "Manage webhooks",
"payments": "Payments", "payments": "Payments",
"viewAnalytics": "View analytics", "viewAnalytics": "View analytics",
"viewApiKey": "View the API key", "viewApiKey": "View API keys",
"viewInvoices": "View invoices", "viewInvoices": "View invoices",
"viewPayments": "View payments", "viewPayments": "View payments",
"viewPayouts": "View payouts", "viewPayouts": "View payouts",

View File

@ -115,7 +115,7 @@
"manageWebhooks": "Управление Webhooks", "manageWebhooks": "Управление Webhooks",
"payments": "Платежи", "payments": "Платежи",
"viewAnalytics": "Просмотр аналитики", "viewAnalytics": "Просмотр аналитики",
"viewApiKey": "Просмотр API ключа", "viewApiKey": "Просмотр API ключей",
"viewInvoices": "Просмотр инвойсов", "viewInvoices": "Просмотр инвойсов",
"viewPayments": "Просмотр платежей", "viewPayments": "Просмотр платежей",
"viewPayouts": "Просмотр возмещений", "viewPayouts": "Просмотр возмещений",