mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 02:25:23 +00:00
FRONTEND-534: Accept invitation page (#442)
This commit is contained in:
parent
c84efbedaf
commit
1dfcc0e45c
@ -0,0 +1,27 @@
|
||||
<div
|
||||
*transloco="let t; scope: 'organizations'; read: 'organizations.acceptInvitation'"
|
||||
fxLayout="column"
|
||||
fxLayoutGap="48px"
|
||||
fxLayoutAlign=" center"
|
||||
class="dsh-accept-invitation"
|
||||
>
|
||||
<dsh-spinner *ngIf="isLoading$ | async; else loaded"></dsh-spinner>
|
||||
<ng-template #loaded>
|
||||
<ng-container *ngIf="isCompleted; else confirm">
|
||||
<ng-container *ngIf="hasError; else success">
|
||||
<div class="dsh-headline">{{ t('error.title') }}</div>
|
||||
</ng-container>
|
||||
<ng-template #success>
|
||||
<div class="dsh-headline">{{ t('success.title') }}</div>
|
||||
<a dshLink routerLink="/organizations">{{ t('success.goToOrganizationsList') }}</a>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #confirm>
|
||||
<div class="dsh-headline">{{ t('confirm.title') }}</div>
|
||||
<button dsh-button (click)="accept()" color="accent" size="lg" class="accept-button">
|
||||
{{ t('confirm.button') }}
|
||||
</button>
|
||||
<div dshTextColor="secondary" class="dsh-caption">{{ t('confirm.refuseDescription') }}</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
.dsh-accept-invitation {
|
||||
margin-top: 260px;
|
||||
}
|
||||
|
||||
.accept-button {
|
||||
width: 180px;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { anything, deepEqual, mock, verify, when } from 'ts-mockito';
|
||||
@ -15,14 +15,11 @@ describe('AcceptInvitationComponent', () => {
|
||||
let fixture: ComponentFixture<AcceptInvitationComponent>;
|
||||
let component: AcceptInvitationComponent;
|
||||
let mockRoute: ActivatedRoute;
|
||||
let mockRouter: Router;
|
||||
let mockOrganizationsService: OrganizationsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockRouter = mock(Router);
|
||||
|
||||
mockRoute = mock(ActivatedRoute);
|
||||
when(mockRoute.snapshot).thenReturn({ params: { token: '123' } } as any);
|
||||
when(mockRoute.params).thenReturn(of({ token: '123' } as any));
|
||||
|
||||
mockOrganizationsService = mock(OrganizationsService);
|
||||
when(mockOrganizationsService.joinOrg(anything())).thenReturn(of({} as any));
|
||||
@ -34,7 +31,6 @@ describe('AcceptInvitationComponent', () => {
|
||||
provideMockService(OrganizationsService, mockOrganizationsService),
|
||||
provideMockService(ErrorService),
|
||||
provideMockService(ActivatedRoute, mockRoute),
|
||||
provideMockService(Router, mockRouter),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
@ -48,9 +44,11 @@ describe('AcceptInvitationComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be init', () => {
|
||||
verify(mockOrganizationsService.joinOrg(deepEqual({ invitation: '123' }))).once();
|
||||
verify(mockRouter.navigate(deepEqual(['organizations']))).once();
|
||||
expect().nothing();
|
||||
describe('accept method', () => {
|
||||
it('should be join to org', () => {
|
||||
component.accept();
|
||||
verify(mockOrganizationsService.joinOrg(deepEqual({ invitation: '123' }))).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,36 +1,46 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { first, pluck, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { OrganizationsService } from '@dsh/api';
|
||||
import { ErrorService } from '@dsh/app/shared';
|
||||
import { inProgressTo } from '@dsh/utils';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'dsh-accept-invitation',
|
||||
template: ``,
|
||||
templateUrl: 'accept-invitation.component.html',
|
||||
styleUrls: ['accept-invitation.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AcceptInvitationComponent implements OnInit {
|
||||
export class AcceptInvitationComponent {
|
||||
hasError = false;
|
||||
isCompleted = false;
|
||||
isLoading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private organizationsService: OrganizationsService,
|
||||
private errorService: ErrorService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.acceptInvitation();
|
||||
}
|
||||
|
||||
private acceptInvitation() {
|
||||
const invitation = this.route.snapshot.params.token;
|
||||
this.organizationsService
|
||||
.joinOrg({ invitation })
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe(
|
||||
() => this.router.navigate(['organizations']),
|
||||
(err) => this.errorService.error(err)
|
||||
);
|
||||
@inProgressTo('isLoading$')
|
||||
accept(): Subscription {
|
||||
return this.route.params
|
||||
.pipe(
|
||||
first(),
|
||||
pluck('token'),
|
||||
switchMap((invitation: string) => this.organizationsService.joinOrg({ invitation })),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
this.errorService.error(err, false);
|
||||
this.hasError = true;
|
||||
},
|
||||
})
|
||||
.add(() => (this.isCompleted = true));
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,27 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { TranslocoModule } from '@ngneat/transloco';
|
||||
|
||||
import { ErrorModule } from '@dsh/app/shared';
|
||||
import { ButtonModule } from '@dsh/components/buttons';
|
||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||
import { LinkModule } from '@dsh/components/link';
|
||||
|
||||
import { AcceptInvitationRoutingModule } from './accept-invitation-routing.module';
|
||||
import { AcceptInvitationComponent } from './accept-invitation.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, AcceptInvitationRoutingModule, ErrorModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AcceptInvitationRoutingModule,
|
||||
ErrorModule,
|
||||
TranslocoModule,
|
||||
ButtonModule,
|
||||
FlexModule,
|
||||
IndicatorsModule,
|
||||
LinkModule,
|
||||
],
|
||||
declarations: [AcceptInvitationComponent],
|
||||
exports: [AcceptInvitationComponent],
|
||||
})
|
||||
|
@ -1,27 +1,35 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
|
||||
import { ErrorResult } from '@dsh/app/shared/services/error/models/error-result';
|
||||
|
||||
import { NotificationService } from '../notification';
|
||||
import { CommonError } from './models/common-error';
|
||||
|
||||
// TODO: collect error information
|
||||
@Injectable()
|
||||
export class ErrorService {
|
||||
constructor(private notificationService: NotificationService, private transloco: TranslocoService) {}
|
||||
|
||||
error(error: Error): MatSnackBarRef<SimpleSnackBar> {
|
||||
// TODO: collect and dev log error information
|
||||
error(error: unknown, notify = true): ErrorResult {
|
||||
const errorResult: ErrorResult = { error: this.parse(error) };
|
||||
if (notify) {
|
||||
errorResult.notification = this.notificationService.error(errorResult.error.message);
|
||||
}
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
private parse(error: unknown): CommonError {
|
||||
if (error instanceof CommonError) {
|
||||
return error;
|
||||
}
|
||||
if (error instanceof TypeError) {
|
||||
return this.notificationService.error(this.transloco.translate('notification.error'));
|
||||
return new CommonError(this.transloco.translate('notification.error'));
|
||||
}
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
return this.notificationService.error(this.transloco.translate('notification.httpError'));
|
||||
return new CommonError(this.transloco.translate('notification.httpError'));
|
||||
}
|
||||
if (error instanceof CommonError) {
|
||||
return this.notificationService.error(error.message);
|
||||
}
|
||||
|
||||
return this.notificationService.error(this.transloco.translate('notification.error'));
|
||||
return new CommonError(this.transloco.translate('notification.error'));
|
||||
}
|
||||
}
|
||||
|
8
src/app/shared/services/error/models/error-result.ts
Normal file
8
src/app/shared/services/error/models/error-result.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { CommonError } from '@dsh/app/shared';
|
||||
|
||||
export interface ErrorResult {
|
||||
error: CommonError;
|
||||
notification?: MatSnackBarRef<SimpleSnackBar>;
|
||||
}
|
@ -131,5 +131,19 @@
|
||||
"editRolesDialog": {
|
||||
"title": "Редактирование ролей",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"acceptInvitation": {
|
||||
"confirm": {
|
||||
"title": "Подтвердите вступление в организацию",
|
||||
"button": "Подтвердить",
|
||||
"refuseDescription": "Для отказа просто покиньте эту страницу"
|
||||
},
|
||||
"success": {
|
||||
"title": "Вы успешно вступили в организацию",
|
||||
"goToOrganizationsList": "Перейти к списку организаций"
|
||||
},
|
||||
"error": {
|
||||
"title": "Произошла ошибка"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
import { Directive, HostBinding, Input } from '@angular/core';
|
||||
|
||||
import { Color } from '@dsh/components/indicators/text-color/types/color';
|
||||
|
||||
const PREFIX = 'dsh-text-color';
|
||||
|
||||
@Directive({
|
||||
selector: '[dshTextColor]',
|
||||
})
|
||||
export class TextColorDirective {
|
||||
@Input() dshTextColor: 'primary' | 'secondary';
|
||||
@Input() dshTextColor: keyof Color | Color;
|
||||
|
||||
@HostBinding('attr.class') get class(): string {
|
||||
const prefix = 'dsh-text-color';
|
||||
switch (this.dshTextColor) {
|
||||
case 'primary':
|
||||
return `${prefix}-primary`;
|
||||
case 'secondary':
|
||||
return `${prefix}-secondary`;
|
||||
}
|
||||
@HostBinding(`class.${PREFIX}-${Color.Primary}`) get primaryColor() {
|
||||
return this.dshTextColor === Color.Primary;
|
||||
}
|
||||
|
||||
@HostBinding(`class.${PREFIX}-${Color.Secondary}`) get secondaryColor() {
|
||||
return this.dshTextColor === Color.Secondary;
|
||||
}
|
||||
}
|
||||
|
4
src/components/indicators/text-color/types/color.ts
Normal file
4
src/components/indicators/text-color/types/color.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum Color {
|
||||
Primary = 'primary',
|
||||
Secondary = 'secondary',
|
||||
}
|
13
src/components/link/_link-theme.scss
Normal file
13
src/components/link/_link-theme.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin dsh-link-theme($theme) {
|
||||
.dsh-link {
|
||||
color: map-get($theme, primary, default);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dsh-link-typography($config) {
|
||||
.dsh-link {
|
||||
@include mat-typography-level-to-styles($config, body-1);
|
||||
}
|
||||
}
|
2
src/components/link/index.ts
Normal file
2
src/components/link/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './link.directive';
|
||||
export * from './link.module';
|
8
src/components/link/link.directive.ts
Normal file
8
src/components/link/link.directive.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Directive, HostBinding } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'a[dshLink]',
|
||||
})
|
||||
export class LinkDirective {
|
||||
@HostBinding(`class.dsh-link`) linkClass = true;
|
||||
}
|
9
src/components/link/link.module.ts
Normal file
9
src/components/link/link.module.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { LinkDirective } from './link.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [LinkDirective],
|
||||
exports: [LinkDirective],
|
||||
})
|
||||
export class LinkModule {}
|
@ -29,6 +29,7 @@
|
||||
@import '../../components/nested-table/nested-table-theme';
|
||||
@import '../../components/layout/headline/headline-theme';
|
||||
@import '../../components/indicators/selection/selection-theme';
|
||||
@import '../../components/link/link-theme';
|
||||
|
||||
@import '../../app/home/home-theme';
|
||||
@import '../../app/home/actionbar/actionbar-theme';
|
||||
@ -93,4 +94,5 @@
|
||||
@include dsh-breadcrumb-theme($theme);
|
||||
@include dsh-text-color-theme($theme);
|
||||
@include dsh-selection-theme($theme);
|
||||
@include dsh-link-theme($theme);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
@import '../../components/indicators/last-updated/last-updated-theme';
|
||||
@import '../../components/global-banner/global-banner-theme';
|
||||
@import '../../components/navigation-link/navigation-link-theme';
|
||||
@import '../../components/link/link-theme';
|
||||
|
||||
@import '../../app/home/home-theme';
|
||||
@import '../../app/home/welcome-image/welcome-image-theme';
|
||||
@ -55,4 +56,5 @@
|
||||
@include dsh-link-label-typography($config);
|
||||
@include dsh-navigation-link-typography($config);
|
||||
@include dsh-breadcrumb-typography($config);
|
||||
@include dsh-link-typography($config);
|
||||
}
|
||||
|
@ -116,6 +116,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dsh-display-3,
|
||||
#{$selector} .dsh-display-3 {
|
||||
@include mat-typography-level-to-styles($config, display-3);
|
||||
margin: 0;
|
||||
|
Loading…
Reference in New Issue
Block a user