From 338776440ffe3ac61ede403785dbde2e7978d84c Mon Sep 17 00:00:00 2001 From: Meskat Date: Tue, 30 Apr 2019 17:02:29 +0200 Subject: [PATCH 1/6] PAN-290: Responsiveness implementation, panel should usable on tablets and larger screens - make unit list and banners list scrollable - improve tables presentation on smaller screens - minor ux and style improvements --- .../ad-list/ad-list.component.html | 8 +-- .../ad-list/ad-list.component.scss | 6 +- .../campaign-details.component.html | 15 ++--- .../campaign-details.component.scss | 8 +++ .../campaign-list.component.html | 1 - .../campaign-list.component.scss | 1 - src/app/common/common.module.ts | 14 +++-- .../table-navigation.component.scss | 1 - .../dashboard/dashboard.component.html | 3 +- .../ad-units/ad-units.component.html | 32 +++++----- .../ad-units/ad-units.component.scss | 29 +++++++-- .../site-details/site-details.component.html | 62 +++++++------------ .../site-details/site-details.component.scss | 13 ++++ .../site-list/site-list.component.html | 4 -- .../billing-history.component.html | 2 +- .../billing-history.component.scss | 4 ++ .../user-wallet/user-wallet.component.scss | 2 +- .../account-settings.component.scss | 1 + .../settings-navigation.component.scss | 2 +- src/styles/_buttons.scss | 6 ++ src/styles/_common.scss | 6 +- src/styles/_flexbox.scss | 7 +-- src/styles/_mixins.scss | 4 +- 23 files changed, 125 insertions(+), 106 deletions(-) diff --git a/src/app/advertiser/campaign-details/ad-list/ad-list.component.html b/src/app/advertiser/campaign-details/ad-list/ad-list.component.html index 0c2257565..ac38473d7 100644 --- a/src/app/advertiser/campaign-details/ad-list/ad-list.component.html +++ b/src/app/advertiser/campaign-details/ad-list/ad-list.component.html @@ -1,9 +1,5 @@ -
- +
+
@@ -283,7 +279,6 @@ class=" dwmth-btn dwmth-btn--edit - hidden-md no-wrap" (click)="navigateToCampaignEdition('basic-information', 1)" data-test="advertiser-campaign-edit-basic-info-button"> @@ -304,8 +299,7 @@
diff --git a/src/app/publisher/site-details/ad-units/ad-units.component.html b/src/app/publisher/site-details/ad-units/ad-units.component.html index 8dff96f9d..0992b1130 100644 --- a/src/app/publisher/site-details/ad-units/ad-units.component.html +++ b/src/app/publisher/site-details/ad-units/ad-units.component.html @@ -1,8 +1,8 @@ -
- + + - - - - - + + + - + + + + diff --git a/src/app/admin/user-list/user-list-item/user-list-item.component.scss b/src/app/admin/user-list/user-list-item/user-list-item.component.scss index 1b3e80dbe..caa5a603f 100644 --- a/src/app/admin/user-list/user-list-item/user-list-item.component.scss +++ b/src/app/admin/user-list/user-list-item/user-list-item.component.scss @@ -1,9 +1,29 @@ @import '../../../../styles.scss'; -.positive { - color: pal(green); -} +.user-list-item { + @include container($border-color: pal(gray, nearly-white)); + display: flex; + justify-content: space-between; + + &__cell { + @include copy($font-size: 24); + display: flex; + align-items: center; + + &--bold { + @include copy( + $color: pal(navy, base), + $font-weight: semi, + $font-size: 24 + ); + } + } -.negative { - color: pal(red); + &__btn { + @include btn( + $border-color: pal(navy, light), + $color: pal(navy, light), + $background-color: pal(white) + ); + } } diff --git a/src/app/admin/user-list/user-list-item/user-list-item.component.ts b/src/app/admin/user-list/user-list-item/user-list-item.component.ts index 38201e7c6..6da94188f 100644 --- a/src/app/admin/user-list/user-list-item/user-list-item.component.ts +++ b/src/app/admin/user-list/user-list-item/user-list-item.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { UserInfoStats } from 'models/settings.model'; @@ -9,4 +9,8 @@ import { UserInfoStats } from 'models/settings.model'; }) export class UserListItemComponent { @Input() userInfoStats: UserInfoStats; + + handleImpersonating() { + // impersonating happens + } } diff --git a/src/app/admin/user-list/user-list.component.html b/src/app/admin/user-list/user-list.component.html index ef10f53b1..575e2d15c 100644 --- a/src/app/admin/user-list/user-list.component.html +++ b/src/app/admin/user-list/user-list.component.html @@ -1,5 +1,6 @@
@@ -21,7 +22,6 @@ dwmth-heading--primary" > Users - ({{ userCount }})
-
- - -
+ + + + + + + + + + + + + + + + + + + + + + + + + +
-
+
{{ adUnit.shortHeadline }}
{{ adUnit.shortHeadline }} - {{ adUnit.size.label }} - {{ adUnit.size.width }} x {{adUnit.size.height }} + {{ adUnit.size.label }} + {{ adUnit.size.width }} x {{adUnit.size.height }}
{{ (adUnit.revenue || 0) | formatMoney:2 }} {{ (adUnit.clicks || 0) }} {{ (adUnit.impressions || 0) }} {{ (adUnit.ctr || 0) | percent:'1.2-2' }} {{ (adUnit.clicks || 0) }} {{ (adUnit.impressions || 0) }} {{ (adUnit.ctr || 0) | percent:'1.2-2' }} {{ (adUnit.averageRpm || 0) | formatMoney:2 }} {{ (adUnit.averageRpc || 0) | formatMoney:2 }} - -
+
@@ -228,8 +221,7 @@ class=" dwmth-btn dwmth-btn--edit - dwmth-btn--no-border - hidden-md" + dwmth-btn--no-border" (click)="navigateToEditSite('additional-filtering', 2)" data-test="publisher-site-edit-filtering-button" > @@ -292,7 +284,7 @@
+ class="col-xs-12">

Classification

@@ -336,8 +328,7 @@ class=" dwmth-btn dwmth-btn--edit - dwmth-btn--no-border - hidden-md" + dwmth-btn--no-border" (click)="navigateToEditSite('create-ad-units', 3)" data-test="publisher-site-edit-ad-units-button" > @@ -351,33 +342,28 @@ Edit Ad Units
- - - - -
+
+ + + +
+
+ {{ userInfoStats.email }} + + {{ + userInfoStats.isAdvertiser && userInfoStats.isPublisher ? 'Adv / Pub' : + userInfoStats.isAdvertiser && !userInfoStats.isPublisher ? 'Advertiser' : 'Publisher' + }} + + +
- - - - - + + + +
+ + -
+
diff --git a/src/app/admin/user-list/user-list.component.ts b/src/app/admin/user-list/user-list.component.ts index 3613fb161..299d92f4e 100644 --- a/src/app/admin/user-list/user-list.component.ts +++ b/src/app/admin/user-list/user-list.component.ts @@ -3,12 +3,11 @@ import { Store } from '@ngrx/store'; import { HandleSubscription } from 'common/handle-subscription'; import { AppState } from 'models/app-state.model'; -import { UserInfoStats } from 'models/settings.model'; +import { UserInfoStats, Users } from 'models/settings.model'; import { sortArrayByColumnMetaData } from 'common/utilities/helpers'; import { TableColumnMetaData } from 'models/table.model'; import * as adminActions from 'store/admin/admin.actions'; import { appSettings } from 'app-settings'; - @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', @@ -16,10 +15,8 @@ import { appSettings } from 'app-settings'; }) export class UserListComponent extends HandleSubscription implements OnInit { userSearch = ''; - users: UserInfoStats[]; + users: Users; filteredUsers: UserInfoStats[]; - - userCount: number; userTypes = appSettings.USER_TYPES; selectedType = 'All'; @@ -31,12 +28,11 @@ export class UserListComponent extends HandleSubscription implements OnInit { const usersSubscription = this.store.select('state', 'admin', 'users') .subscribe(users => { this.users = users; - this.filteredUsers = [...users]; - this.userCount = this.users.length; + this.filteredUsers = this.users && this.users.data; }); this.subscriptions.push(usersSubscription); - this.store.dispatch(new adminActions.LoadUsers('')); + this.store.dispatch(new adminActions.LoadUsers()); } filterUsersByType(type, resetSearch = false) { @@ -46,7 +42,7 @@ export class UserListComponent extends HandleSubscription implements OnInit { this.userSearch = ''; } - this.filteredUsers = this.users.filter(user => { + this.filteredUsers = this.users.data.filter(user => { switch (type) { case 'All': return true; @@ -75,4 +71,10 @@ export class UserListComponent extends HandleSubscription implements OnInit { sortTable(columnMetaData: TableColumnMetaData) { this.filteredUsers = sortArrayByColumnMetaData(this.filteredUsers, columnMetaData); } + + handlePaginationEvent(e): void { + const payload = this.users.prevPageUrl && this.users.currentPage >= e.pageIndex + 1 ? this.users.prevPageUrl + : this.users.nextPageUrl; + this.store.dispatch(new adminActions.LoadUsers(payload)); + } } diff --git a/src/app/common/components/table-navigation/table-navigation.component.scss b/src/app/common/components/table-navigation/table-navigation.component.scss index c374ac409..87645ef66 100644 --- a/src/app/common/components/table-navigation/table-navigation.component.scss +++ b/src/app/common/components/table-navigation/table-navigation.component.scss @@ -1,7 +1,7 @@ @import "../../../../styles"; .table-nav { - @include container(); + @include container($border-color: pal(gray, nearly-white)); display: flex; justify-content: space-between; width: 100%; diff --git a/src/app/common/components/table-navigation/table-navigation.component.ts b/src/app/common/components/table-navigation/table-navigation.component.ts index 71b61242a..e58af102c 100644 --- a/src/app/common/components/table-navigation/table-navigation.component.ts +++ b/src/app/common/components/table-navigation/table-navigation.component.ts @@ -51,10 +51,9 @@ export class TableNavigationComponent implements OnInit { ]; userListNavigationItems = [ - {title: '', columnWidth: 'col-xs-4'}, - {title: '', columnWidth: 'col-xs-2'}, - {title: 'Profit', columnWidth: 'col-xs-2', keys: ['profit'], sortAsc: true}, - {title: 'Top used keywords', columnWidth: 'col-xs-4'} + {title: 'email', columnWidth: 'col-xs-4'}, + {title: 'role', columnWidth: 'col-xs-2'}, + {title: 'Impersonate', columnWidth: 'col-xs-2'} ]; adUnitsNavigation = [ diff --git a/src/app/models/app-state.model.ts b/src/app/models/app-state.model.ts index c9c361f86..0575f717d 100644 --- a/src/app/models/app-state.model.ts +++ b/src/app/models/app-state.model.ts @@ -6,7 +6,7 @@ import { License, NotificationItem, TermsAndPrivacy, - UserInfoStats, + Users, } from './settings.model'; import { User } from './user.model'; import { ChartFilterSettings } from './chart/chart-filter-settings.model'; @@ -48,7 +48,7 @@ interface UserState { } interface AdminState { - users: UserInfoStats[]; + users: Users; settings: AdminSettings; termsAndPrivacy: TermsAndPrivacy; license: License | null; diff --git a/src/app/models/settings.model.ts b/src/app/models/settings.model.ts index ff1bd77d2..566f571a1 100644 --- a/src/app/models/settings.model.ts +++ b/src/app/models/settings.model.ts @@ -23,12 +23,25 @@ interface NotificationItem { } interface UserInfoStats { - id: number; + uuid: string; email: string; isAdvertiser: boolean; isPublisher: boolean; - profit: number; - topKeywords: string[]; +} + +interface Users { + data: UserInfoStats[], + currentPage: number, + firstPageUrl: string, + from: number, + lastPage: number, + lastPageUrl: string, + nextPageUrl: string, + path: string, + perPage: number, + prevPageUrl: string | null, + to: number, + total: number, } interface AdminSettings { @@ -83,6 +96,7 @@ export { BillingHistoryItem, BillingHistory, NotificationItem, + Users, UserInfoStats, AdminSettings, AdsharesAddress, diff --git a/src/app/store/admin/admin.actions.ts b/src/app/store/admin/admin.actions.ts index 91126920f..89849a1a8 100644 --- a/src/app/store/admin/admin.actions.ts +++ b/src/app/store/admin/admin.actions.ts @@ -41,7 +41,7 @@ export const GET_LICENSE_FAILURE = 'Get license failure'; export class LoadUsers implements Action { readonly type: string = LOAD_USERS; - constructor(public payload: any) { + constructor(public payload?: string) { } } diff --git a/src/app/store/admin/admin.effects.ts b/src/app/store/admin/admin.effects.ts index e2eeec567..644f3adce 100644 --- a/src/app/store/admin/admin.effects.ts +++ b/src/app/store/admin/admin.effects.ts @@ -35,6 +35,7 @@ import { AdminService } from 'admin/admin.service'; import { Observable } from "rxjs"; import { ClickToADSPipe } from "common/pipes/adshares-token.pipe"; import { HTTP_NOT_FOUND } from "common/utilities/codes"; +import "rxjs/add/operator/debounceTime"; @Injectable() export class AdminEffects { @@ -48,7 +49,9 @@ export class AdminEffects { @Effect() loadUsers$ = this.actions$ .ofType(LOAD_USERS) - .switchMap(() => this.service.getUsers() + .debounceTime(100) + .map(toPayload) + .switchMap((nextPage) => this.service.getUsers(nextPage) .map((users) => new LoadUsersSuccess(users)) .catch((err) => Observable.of(new LoadUsersFailure(err))) ); diff --git a/src/app/store/admin/admin.reducers.ts b/src/app/store/admin/admin.reducers.ts index d44f14828..684fdfd95 100644 --- a/src/app/store/admin/admin.reducers.ts +++ b/src/app/store/admin/admin.reducers.ts @@ -11,7 +11,7 @@ import { import { AdminState } from 'models/app-state.model'; const initialState: AdminState = { - users: [], + users: null, settings: { adserverName: '', coldWalletAddress: '', @@ -36,7 +36,7 @@ export function adminReducers(state = initialState, action: actions) { case LOAD_USERS_SUCCESS: return { ...state, - ...action.payload + users: action.payload }; case GET_LICENSE_SUCCESS: if (action.payload.status !== 1) { diff --git a/src/styles/_material-override.scss b/src/styles/_material-override.scss index f67b0279b..ec0f3de63 100644 --- a/src/styles/_material-override.scss +++ b/src/styles/_material-override.scss @@ -214,7 +214,7 @@ mat-chip:not(.mat-basic-chip) { margin-right: 8px; } -.classifier .mat-paginator-container { +.mat-paginator-container { margin-top: 2px; border: 1px solid pal(gray, nearly-white); } diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index b84dfb76e..a6510193b 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -83,6 +83,8 @@ &[disabled] { background-color: lighten($background-color, 5%); + border-color: lighten($border-color, 5%); + cursor: not-allowed; } } From 299756e048a319da381e92bd80f3c9e1b9fe9d19 Mon Sep 17 00:00:00 2001 From: Maciej Pilarczyk Date: Fri, 10 May 2019 15:36:42 +0200 Subject: [PATCH 5/6] True Excel format reports --- src/app/common/utilities/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/common/utilities/helpers.ts b/src/app/common/utilities/helpers.ts index 47c1a6e83..420f397e5 100644 --- a/src/app/common/utilities/helpers.ts +++ b/src/app/common/utilities/helpers.ts @@ -185,8 +185,8 @@ const adjustCampaignStatus = (campaignInfo, currentDate): number => { function downloadCSVFile(data, from, to) { const formattedFrom = moment(from).format(DATE_FORMAT); const formattedTo = moment(to).format(DATE_FORMAT); - const fileName = `report_${formattedFrom}_${formattedTo}.csv`; - const blob = new Blob([data], {type: 'text/csv;charset=utf-8'}); + const fileName = `report_${formattedFrom}_${formattedTo}.xlsx`; + const blob = new Blob([data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const link = document.createElement('a'); link.setAttribute("download", fileName); link.setAttribute("href", URL.createObjectURL(blob)); From 1dd6756a03797ea84bca24010676ae1681c58922 Mon Sep 17 00:00:00 2001 From: Meskat Date: Fri, 10 May 2019 17:44:27 +0200 Subject: [PATCH 6/6] Pan 389 (#652) * Changelog * UserList * PAN-389: Users list component * PAN-389: Handle impersonation --- src/app/admin/admin.service.ts | 4 +++ .../user-list-item.component.html | 1 - .../user-list-item.component.scss | 8 +++-- .../user-list-item.component.ts | 22 ++++++++++++-- .../admin/user-list/user-list.component.html | 19 +++++++----- .../admin/user-list/user-list.component.scss | 15 ++++++++++ .../admin/user-list/user-list.component.ts | 30 +++++++++++++++++-- src/app/auth/login/login.component.ts | 2 +- .../components/header/header.component.html | 21 +++++++++++-- .../components/header/header.component.ts | 25 +++++++++++----- src/app/common/request.interceptor.ts | 30 ++++++++++++++++++- src/app/models/app-state.model.ts | 1 + src/app/models/initial-state/user.ts | 1 + src/app/models/settings.model.ts | 1 + src/app/models/user.model.ts | 1 + src/app/session.service.ts | 15 +++++++++- src/app/store/auth/auth.actions.ts | 18 ++++++++++- src/app/store/auth/auth.reducers.ts | 15 ++++++++-- src/app/store/common/common.reducers.ts | 3 +- src/environments/environment.ts | 2 +- src/styles/_material-override.scss | 1 - 21 files changed, 200 insertions(+), 35 deletions(-) diff --git a/src/app/admin/admin.service.ts b/src/app/admin/admin.service.ts index f8c86b32c..245cd5593 100644 --- a/src/app/admin/admin.service.ts +++ b/src/app/admin/admin.service.ts @@ -22,6 +22,10 @@ export class AdminService { return this.http.get(url); } + impersonateUser(id: number): Observable { + return this.http.get(`${environment.serverUrl}/admin/impersonation/${id}`) + } + getAdminSettings(): Observable { return this.http.get(`${environment.serverUrl}/admin/settings`); } diff --git a/src/app/admin/user-list/user-list-item/user-list-item.component.html b/src/app/admin/user-list/user-list-item/user-list-item.component.html index 2623ce485..41fc7a06a 100644 --- a/src/app/admin/user-list/user-list-item/user-list-item.component.html +++ b/src/app/admin/user-list/user-list-item/user-list-item.component.html @@ -14,7 +14,6 @@ diff --git a/src/app/admin/user-list/user-list-item/user-list-item.component.scss b/src/app/admin/user-list/user-list-item/user-list-item.component.scss index caa5a603f..369334730 100644 --- a/src/app/admin/user-list/user-list-item/user-list-item.component.scss +++ b/src/app/admin/user-list/user-list-item/user-list-item.component.scss @@ -6,7 +6,7 @@ justify-content: space-between; &__cell { - @include copy($font-size: 24); + @include copy($font-size: 16); display: flex; align-items: center; @@ -14,7 +14,7 @@ @include copy( $color: pal(navy, base), $font-weight: semi, - $font-size: 24 + $font-size: 16 ); } } @@ -23,7 +23,9 @@ @include btn( $border-color: pal(navy, light), $color: pal(navy, light), - $background-color: pal(white) + $background-color: pal(white), + $padding-horizontal: 12px, + $padding-vertical: 8px ); } } diff --git a/src/app/admin/user-list/user-list-item/user-list-item.component.ts b/src/app/admin/user-list/user-list-item/user-list-item.component.ts index 6da94188f..dde2b79fe 100644 --- a/src/app/admin/user-list/user-list-item/user-list-item.component.ts +++ b/src/app/admin/user-list/user-list-item/user-list-item.component.ts @@ -1,6 +1,11 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { UserInfoStats } from 'models/settings.model'; +import { AdminService } from "admin/admin.service"; +import { Store } from "@ngrx/store"; +import { AppState } from "models/app-state.model"; +import { ImpersonateUser } from "store/auth/auth.actions"; +import { Router } from "@angular/router"; @Component({ selector: 'app-user-list-item', @@ -10,7 +15,20 @@ import { UserInfoStats } from 'models/settings.model'; export class UserListItemComponent { @Input() userInfoStats: UserInfoStats; + constructor( + private adminService: AdminService, + private store: Store, + private router: Router, + ) { + } + handleImpersonating() { - // impersonating happens + this.adminService.impersonateUser(this.userInfoStats.id).subscribe( + (token) => { + this.store.dispatch(new ImpersonateUser(token)) + this.router.navigate([`/${'publisher'.toLowerCase()}`, 'dashboard']) + + } + ) } } diff --git a/src/app/admin/user-list/user-list.component.html b/src/app/admin/user-list/user-list.component.html index 575e2d15c..9b4195a3c 100644 --- a/src/app/admin/user-list/user-list.component.html +++ b/src/app/admin/user-list/user-list.component.html @@ -102,23 +102,26 @@ [navigationName]="'userListNavigation'" (sortTable)="sortTable($event)" > - + + +
+ +
+
+ - -
- -
-
diff --git a/src/app/admin/user-list/user-list.component.scss b/src/app/admin/user-list/user-list.component.scss index c6b41759d..d85de607c 100644 --- a/src/app/admin/user-list/user-list.component.scss +++ b/src/app/admin/user-list/user-list.component.scss @@ -12,6 +12,21 @@ margin: 10px 0; } } + &__items { + position: relative; + height: calc((54px * 15) + 40px); + } + + &__loader-container { + height: calc(54px * 15); + display: flex; + width: 100%; + background: rgba(255, 255, 255, 0.9); + align-items: center; + justify-content: center; + position: absolute; + top: 40px; + } } .choose-user-type { diff --git a/src/app/admin/user-list/user-list.component.ts b/src/app/admin/user-list/user-list.component.ts index 299d92f4e..c164e6005 100644 --- a/src/app/admin/user-list/user-list.component.ts +++ b/src/app/admin/user-list/user-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; - +import { trigger, transition, style, animate, } from '@angular/animations'; import { HandleSubscription } from 'common/handle-subscription'; import { AppState } from 'models/app-state.model'; import { UserInfoStats, Users } from 'models/settings.model'; @@ -8,10 +8,29 @@ import { sortArrayByColumnMetaData } from 'common/utilities/helpers'; import { TableColumnMetaData } from 'models/table.model'; import * as adminActions from 'store/admin/admin.actions'; import { appSettings } from 'app-settings'; + @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', - styleUrls: ['./user-list.component.scss'] + styleUrls: ['./user-list.component.scss'], + animations: [ + trigger( + 'fadeIn', + [ + transition( + ':enter', [ + style({opacity: 0}), + animate('400ms', style({'opacity': 1})) + ] + ), + transition( + ':leave', [ + style({opacity: 1}), + animate('400ms', style({'opacity': 0})) + ] + )] + ) + ], }) export class UserListComponent extends HandleSubscription implements OnInit { userSearch = ''; @@ -19,6 +38,7 @@ export class UserListComponent extends HandleSubscription implements OnInit { filteredUsers: UserInfoStats[]; userTypes = appSettings.USER_TYPES; selectedType = 'All'; + isLoading: boolean = true; constructor(private store: Store) { super(); @@ -28,6 +48,7 @@ export class UserListComponent extends HandleSubscription implements OnInit { const usersSubscription = this.store.select('state', 'admin', 'users') .subscribe(users => { this.users = users; + this.isLoading = !this.users; this.filteredUsers = this.users && this.users.data; }); this.subscriptions.push(usersSubscription); @@ -75,6 +96,11 @@ export class UserListComponent extends HandleSubscription implements OnInit { handlePaginationEvent(e): void { const payload = this.users.prevPageUrl && this.users.currentPage >= e.pageIndex + 1 ? this.users.prevPageUrl : this.users.nextPageUrl; + setTimeout(() => { + this.isLoading = true + }, 100); + this.isLoading = false; + this.store.dispatch(new adminActions.LoadUsers(payload)); } } diff --git a/src/app/auth/login/login.component.ts b/src/app/auth/login/login.component.ts index c6cade9f0..49641d4e9 100644 --- a/src/app/auth/login/login.component.ts +++ b/src/app/auth/login/login.component.ts @@ -16,6 +16,7 @@ import { isUnixTimePastNow } from 'common/utilities/helpers'; import { Store } from "@ngrx/store"; import { AppState } from "models/app-state.model"; import * as authActions from 'store/auth/auth.actions'; +import { ImpersonateUser } from "store/auth/auth.actions"; @Component({ selector: 'app-login', @@ -43,7 +44,6 @@ export class LoginComponent extends HandleSubscription implements OnInit { } ngOnInit() { - // SMELL: this should be elsewhere anyway (?) const user: LocalStorageUser = this.session.getUser(); if (user) { diff --git a/src/app/common/components/header/header.component.html b/src/app/common/components/header/header.component.html index 4f9a6d6e4..ddbabc5cd 100644 --- a/src/app/common/components/header/header.component.html +++ b/src/app/common/components/header/header.component.html @@ -1,3 +1,20 @@ +
+
+ info +
+ This is impersonation mode +

You are impersonating user + {{user.email}} +

+
+
+ +
+
{ - this.totalFunds = wallet.totalFunds + const userDataStateSubscription = this.store.select('state', 'user', 'data') + .subscribe((data) => { + this.totalFunds = data.adserverWallet.totalFunds; + this.user = data; }); this.subscriptions.push(userDataStateSubscription); + const impersonationToken = this.session.getImpersonationToken(); + if (impersonationToken) { + this.store.dispatch(new ImpersonateUser(impersonationToken)) + } } navigateToCreateNewAsset() { @@ -79,7 +86,9 @@ export class HeaderComponent extends HandleSubscription implements OnInit { this.auth.logout(); } - toggleNotificationsBar() { - this.notificationsBarOpen = !this.notificationsBarOpen; + dropImpersonation() { + this.router.navigate(['/', 'admin', 'dashboard', 'users']); + this.session.dropImpersonationToken(); + this.store.dispatch(new DropImpersonationToken()) } } diff --git a/src/app/common/request.interceptor.ts b/src/app/common/request.interceptor.ts index 6de4ccfa6..5e9b2cb3f 100644 --- a/src/app/common/request.interceptor.ts +++ b/src/app/common/request.interceptor.ts @@ -14,18 +14,37 @@ import { ErrorResponseDialogComponent } from "common/dialog/error-response-dialo import { PushNotificationsService } from 'common/components/push-notifications/push-notifications.service'; import { pushNotificationTypesEnum } from 'models/enum/push-notification.enum'; import { environment } from "environments/environment.ts"; +import { AppState } from "models/app-state.model"; +import { Store } from "@ngrx/store"; +import { HandleSubscription } from "common/handle-subscription"; @Injectable() -export class RequestInterceptor implements HttpInterceptor { +export class RequestInterceptor extends HandleSubscription implements HttpInterceptor { openedErrorDialogs: number = 0; maxOpenedErrorDialogs: number = 1; + impersonationToken: string | null = null; constructor( private router: Router, private pushNotificationsService: PushNotificationsService, private dialog: MatDialog, private session: SessionService, + private store: Store ) { + super() + } + + checkForImpersonationToken() { + const subscription = this.store.select('state', 'user', 'data', 'impersonationToken') + .subscribe( + (token) => { + if (token !== this.impersonationToken) { + this.session.setImpersonationToken(token); + } + this.impersonationToken = token; + } + ); + this.subscriptions.push(subscription); } dialogError(title, message) { @@ -47,6 +66,7 @@ export class RequestInterceptor implements HttpInterceptor { } intercept(request: HttpRequest, next: HttpHandler): Observable> { + this.checkForImpersonationToken(); if (this.session.getUser() && this.session.getUser().apiToken) { request = request.clone({ setHeaders: { @@ -55,6 +75,14 @@ export class RequestInterceptor implements HttpInterceptor { }); } + if (!!this.impersonationToken) { + request = request.clone({ + setHeaders: { + [`x-adshares-impersonation`]: this.impersonationToken + } + }); + } + if (environment.xdebug) { request = request.clone({ setParams: { diff --git a/src/app/models/app-state.model.ts b/src/app/models/app-state.model.ts index 0575f717d..7cb76c08e 100644 --- a/src/app/models/app-state.model.ts +++ b/src/app/models/app-state.model.ts @@ -60,6 +60,7 @@ interface CommonState { adsharesAddress: string; chartFilterSettings: ChartFilterSettings; notifications: Notification[]; + impersonationToken: string; } export { AppState, UserState, AdvertiserState, PublisherState, SettingsState, AdminState, CommonState }; diff --git a/src/app/models/initial-state/user.ts b/src/app/models/initial-state/user.ts index eff3d790e..9f5c8a3e4 100644 --- a/src/app/models/initial-state/user.ts +++ b/src/app/models/initial-state/user.ts @@ -17,4 +17,5 @@ export const userInitialState: User = { walletBalance: 0, }, exchangeRate: null, + impersonationToken: null, }; diff --git a/src/app/models/settings.model.ts b/src/app/models/settings.model.ts index 566f571a1..b969be813 100644 --- a/src/app/models/settings.model.ts +++ b/src/app/models/settings.model.ts @@ -23,6 +23,7 @@ interface NotificationItem { } interface UserInfoStats { + id: number; uuid: string; email: string; isAdvertiser: boolean; diff --git a/src/app/models/user.model.ts b/src/app/models/user.model.ts index 5598fc8ec..f2fb56801 100644 --- a/src/app/models/user.model.ts +++ b/src/app/models/user.model.ts @@ -20,6 +20,7 @@ interface User { isEmailConfirmed: boolean; password: string; apiToken?: string; + impersonationToken?: string | null; adserverWallet: UserAdserverWallet; exchangeRate: ExchangeRate | null; uuid: string; diff --git a/src/app/session.service.ts b/src/app/session.service.ts index 9cd8cc2f6..cc473c1fd 100644 --- a/src/app/session.service.ts +++ b/src/app/session.service.ts @@ -1,9 +1,10 @@ import {Injectable} from '@angular/core'; import {Notification} from 'models/notification.model'; -import {LocalStorageUser} from 'models/user.model'; +import { LocalStorageUser, User } from 'models/user.model'; import {AppState} from "models/app-state.model"; import {Store} from "@ngrx/store"; +import { tokenName } from "@angular/compiler"; @Injectable() export class SessionService { @@ -16,6 +17,10 @@ export class SessionService { localStorage.removeItem('user'); } + dropImpersonationToken() { + localStorage.removeItem('impersonationItem') + } + getAccountTypeChoice(): string { return localStorage.getItem('accountTypeChoice'); } @@ -70,4 +75,12 @@ export class SessionService { setUser(user: LocalStorageUser) { localStorage.setItem('user', JSON.stringify(user)); } + + setImpersonationToken(token: string) { + localStorage.setItem('impersonationToken', token); + } + + getImpersonationToken(): string { + return localStorage.getItem('impersonationToken'); + } } diff --git a/src/app/store/auth/auth.actions.ts b/src/app/store/auth/auth.actions.ts index a170566f5..53395f2b7 100644 --- a/src/app/store/auth/auth.actions.ts +++ b/src/app/store/auth/auth.actions.ts @@ -1,5 +1,4 @@ import { Action } from '@ngrx/store'; - import { User } from 'models/user.model'; export const SET_USER = 'Set user'; @@ -13,7 +12,22 @@ export const UPDATE_USER_AUTOMATIC_WITHDRAW_AMOUNT = 'User Automatic Withdraw am export const USER_LOG_OUT_SUCCESS = 'User logged out success'; export const USER_LOG_IN_SUCCESS = 'User logged in success'; +export const IMPERSONATE_USER = 'User impersonation success'; +export const DROP_IMPERSONATION_TOKEN = 'Drop user impersonation token success'; + +export class ImpersonateUser implements Action { + readonly type = IMPERSONATE_USER; + + constructor(public payload: string) { + } +} + +export class DropImpersonationToken implements Action { + readonly type = DROP_IMPERSONATION_TOKEN; + constructor(public payload?: any) { + } +} export class SetUser implements Action { readonly type = SET_USER; @@ -73,6 +87,8 @@ export class UserLogInSuccess implements Action { export type actions = SetUser + | DropImpersonationToken + | ImpersonateUser | SetUserSuccess | SetUserFailure | UpdateUserAddress diff --git a/src/app/store/auth/auth.reducers.ts b/src/app/store/auth/auth.reducers.ts index bbb093b61..65a02d78c 100644 --- a/src/app/store/auth/auth.reducers.ts +++ b/src/app/store/auth/auth.reducers.ts @@ -1,6 +1,6 @@ import * as authActions from './auth.actions'; -import {userInitialState} from 'models/initial-state/user.js'; -import { GET_CURRENT_BALANCE_SUCCESS, actions } from "store/settings/settings.actions"; +import { userInitialState } from 'models/initial-state/user.js'; +import { GET_CURRENT_BALANCE_SUCCESS, actions } from "store/settings/settings.actions"; const initialState = userInitialState; @@ -32,9 +32,20 @@ export function authReducers(state = initialState, action: authActions.actions | exchangeRate: action.payload.exchangeRate, adserverWallet: action.payload.adserverWallet }; + case authActions.USER_LOG_OUT_SUCCESS: case authActions.USER_LOG_IN_SUCCESS: return initialState; + case authActions.IMPERSONATE_USER: + return { + ...state, + impersonationToken: action.payload + }; + case authActions.DROP_IMPERSONATION_TOKEN: + return { + ...state, + impersonationToken: '' + }; default: return state; } diff --git a/src/app/store/common/common.reducers.ts b/src/app/store/common/common.reducers.ts index 0d8503910..ba163948b 100644 --- a/src/app/store/common/common.reducers.ts +++ b/src/app/store/common/common.reducers.ts @@ -5,7 +5,8 @@ const initialState = { activeUserType: 1, adsharesAddress: '', chartFilterSettings: chartFilterSettingsInitialState, - notifications: [] + notifications: [], + impersonationToken: null }; export function commonReducers(state = initialState, action: commonActions.actions) { diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 257fb0e67..6a99af370 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,4 +1,4 @@ -let serverUrl = 'https://test-server.e11.click'; // 'http://localhost:8101'; // +let serverUrl = 'https://demo-server.adshares.net'; // 'http://localhost:8101'; // export const environment = { production: false, diff --git a/src/styles/_material-override.scss b/src/styles/_material-override.scss index ec0f3de63..8a266fa8e 100644 --- a/src/styles/_material-override.scss +++ b/src/styles/_material-override.scss @@ -215,7 +215,6 @@ mat-chip:not(.mat-basic-chip) { } .mat-paginator-container { - margin-top: 2px; border: 1px solid pal(gray, nearly-white); }